TSone

组件系统

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 维持了清晰的面向对象组件模型。