Guide
组件系统
TSone 的组件系统允许您将 UI 拆分为独立、可复用的类组件。
组件定义
组件通过继承 Component<Props, State> 定义,并实现 initState()、initStyles() 和 protected render(): VNode。
基本组件结构
import { Component, VNode } from '@geektech/tsone';
interface MessageState {
message: string;
}
class MyComponent extends Component<object, MessageState> {
protected initState(): MessageState {
return { message: 'Hello, TSone!' };
}
protected initStyles(): void {
this.styleManager.addStyle('.my-component', {
selector: '.my-component',
properties: {
color: '#333',
fontSize: '16px',
padding: '10px',
},
});
}
protected render(): VNode {
return {
tag: 'div',
props: { className: 'my-component' },
children: [''],
};
}
}组件生命周期
挂载阶段
beforeMount(): 组件挂载到 DOM 前调用onMounted(): 组件挂载到 DOM 后调用
更新阶段
beforeUpdate(): 组件状态更新前调用onUpdated(): 组件状态更新后调用
卸载阶段
beforeUnmount(): 组件从 DOM 卸载前调用onUnmounted(): 组件从 DOM 卸载后调用
错误处理(错误边界)
在 onErrorCaptured(error, instance) 中捕获后代组件渲染或更新期间抛出的错误。返回 false 表示错误已被处理并停止继续向上传播;返回 true 或 undefined 时错误继续冒泡到更外层的边界。
class ErrorBoundary extends Component<object, { failed: boolean }> {
protected initState() {
return { failed: false };
}
protected initStyles(): void {}
protected onErrorCaptured(): boolean {
this.state.failed = true;
return false;
}
protected render(): VNode {
if (this.state.failed) {
return { tag: 'p', children: ['子组件出错了,已由边界接管'] };
}
return { tag: 'div', children: this.props.children ?? [] };
}
}生命周期示例
class LifecycleComponent extends Component<object, { count: number }> {
protected initState() {
return { count: 0 };
}
protected initStyles(): void {}
protected beforeMount() {
console.log('Component will mount');
}
protected onMounted() {
console.log('Component mounted');
}
protected beforeUpdate() {
console.log('Component will update');
}
protected onUpdated() {
console.log('Component updated');
}
protected render(): VNode {
return {
tag: 'button',
children: ['Count: '],
listeners: {
click: () => this.state.count++,
},
};
}
}组件属性
定义属性接口
interface ButtonProps {
text: string;
disabled?: boolean;
size?: 'small' | 'medium' | 'large';
}
class Button extends Component<ButtonProps, object> {
protected initState() {
return {};
}
protected initStyles(): void {}
protected render(): VNode {
const { text, disabled = false, size = 'medium' } = this.props;
return {
tag: 'button',
props: {
className: `btn btn-${size}` ,
disabled,
},
children: [text],
};
}
}使用组件属性
class ParentComponent extends Component<object, object> {
protected initState() {
return {};
}
protected initStyles(): void {}
protected render(): VNode {
return {
tag: 'div',
children: [
{
component: Button,
props: { text: 'Click Me', size: 'large' },
},
],
};
}
}组件事件
发射事件
class CounterComponent extends Component<object, { count: number }> {
protected initState() {
return { count: 0 };
}
protected initStyles(): void {}
protected handleIncrement() {
this.state.count++;
this.emit('increment', this.state.count);
}
protected render(): VNode {
return {
tag: 'button',
listeners: { click: () => this.handleIncrement() },
children: ['+'],
};
}
}监听事件
class ParentCounter extends Component<object, object> {
protected initState() {
return {};
}
protected initStyles(): void {}
protected handleCounterChange(value: number) {
console.log('Counter changed:', value);
}
protected render(): VNode {
return {
tag: 'div',
children: [
{
component: CounterComponent,
listeners: {
increment: (value: number) => this.handleCounterChange(value),
},
},
],
};
}
}组件嵌套
class App extends Component<object, object> {
protected initState() {
return {};
}
protected initStyles(): void {}
protected render(): VNode {
return {
tag: 'div',
props: { className: 'app' },
children: [
{ tag: 'header', children: [{ component: NavigationComponent }] },
{ tag: 'main', children: [{ component: HomeComponent }] },
],
};
}
}组件状态管理
状态初始化
protected initState() {
return {
count: 0,
user: { name: 'John', age: 30 },
items: ['Item 1', 'Item 2', 'Item 3'],
};
}状态更新
this.state.count = 1;
this.state.user.name = 'Jane';
this.state.items.push('Item 4');状态变更会经调度器在微任务中批量合并:同一批同步修改只触发一次重渲染。需要同步读取最新 DOM 时使用 flushSync(),或在异步代码中 await nextTick()。
组件样式管理
每个组件实例都持有自己的 styleManager。
添加样式
protected initStyles() {
this.styleManager.addStyle('.button', {
selector: '.button',
properties: {
padding: '8px 16px',
backgroundColor: '#007bff',
color: '#fff',
},
});
this.styleManager.addStyle('.button:hover', {
selector: '.button:hover',
properties: { backgroundColor: '#0069d9' },
});
}移除样式
this.styleManager.removeStyle('.button');
this.styleManager.clearStyles();过渡与组件缓存
Transition 在 show 切换时为单个元素播放进入/离开过渡:进入时应用 {name}-enter-from / {name}-enter-to(各配套一个 -active class),离开时应用 {name}-leave-from / {name}-leave-to,并在离开开始 duration 毫秒后移除元素。默认 name 为 transition、duration 为 300。
import { Transition } from '@geektech/tsone';
{
component: Transition,
props: { show: this.state.open, name: 'fade', duration: 300 },
children: [{ tag: 'div', children: ['内容'] }],
}KeepAlive 让所有带 key 的子组件保持挂载:切换 activeKey 时非活跃项通过 display:none 隐藏,状态与 DOM 均保留,适合 tab 切换等需要保留状态的场景。
import { KeepAlive } from '@geektech/tsone';
{
component: KeepAlive,
props: { activeKey: this.state.activeTab },
children: [
{ key: 'tab-a', component: PanelA },
{ key: 'tab-b', component: PanelB },
],
}组件上下文
组件可以通过应用上下文读取全局配置,也可以通过 router getter 访问路由。
获取上下文
const context = this.getContext();
const router = this.router;
router?.push('/about');
console.log(context?.config);最佳实践
组件设计原则
- 单一职责:每个组件只负责一个功能
- 可复用性:设计通用的、可复用的组件
- 可维护性:保持组件代码简洁、清晰
- 性能优化:避免不必要的渲染和计算
组件命名约定
- 组件类名使用 PascalCase
- 组件文件延续现有仓库的命名风格
- 组件样式类名使用 kebab-case
性能优化技巧
- 避免在 render 方法中创建不必要的新对象
- 只将需要响应式更新的属性放在 state 中
- 对复杂派生值使用 computed
- 控制组件嵌套层级
总结
通过类组件、生命周期、事件、插槽和局部样式管理,TSone 维持了清晰的面向对象组件模型。