sunmao-ui/docs/zh/trait.md
2022-06-10 14:06:00 +08:00

252 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Trait 开发文档
在本章节你可以通过一个 Timer Trait 的实例学习到如何实现自定义的 Trait 。
Trait 的开发和 Component 有诸多相似之处。
| | Trait | Component |
| ------------------------------- | ----- | --------- |
| 拥有 Sepc | ✅ | ✅ |
| 拥有 properties, state, methods | ✅ | ✅ |
| 拥有 Implementation | ✅ | ✅ |
| Implementation 是一个函数 | ✅ | ✅ |
Trait 本质上是一个函数。Trait 的作用是为了增加 Component 的能力。如果还是用面向对象来打比方Trait 是 Component 的装饰器。比如说,给 Component 添加样式、添加状态等等。
Trait 的主要思路是,接受一系列参数,执行一些逻辑,返回 TraitResult从而增强 Component 的能力。显然,这是一个纯函数的逻辑。因此,我们推荐把 Trait 实现为纯函数。即便不是纯函数,实现时也需要考虑到 Trait 被反复执行时的结果,尽量避免意料之外的副作用。
> ⚠️ 由于 Trait 是个纯函数,所以不能在 Trait 中使用 React hooks。
由于 Trait 没有自己的 id所以 Trait 和 Component 共享 id。也就是说**Trait 暴露的所有 State 和 Method 都会被添加到 Component 上**,访问和调用它们是和 Component 自己实现的无异。
## Trait 开发教程
接下来,让我们通过一个例子来学习如何开发一个 Trait。我们要实现一个 Timer Trait它具有下面的能力
- 指定一段时长的定时器,在定时器结束后弹出 alert
- 提供清除定时器的功能
- 可选择是否在 Component 挂载后立即开启一个定时器
- 可获取定时器的状态(`waiting`, `finished`, `stopped`
### Trait Spec
#### Trait Spec 示例
首先我们来编写一个 Trait Spec给 Trait 定义一些基本信息,比如版本和名称等:
```jsx
{
version: 'custom/v1',
metadata: {
name: 'timer',
description: 'Create a timer to alert, and could be clear.',
},
spec: {
properties: PropsSpec, // 详见下文
state: StateSpec, // 详见下文
methods, // 详见下文
},
}
```
#### Trait Properties Spec
接下来就可以开始思考要如何设计我们的 Timer Trait首先来设计下需要传入 Timer Trait 的参数,也就是 Properties 的 Spec。
首先需要一个 `time` 参数来控制定时器的时间,和 `content` 参数来展示 alert 的内容。
另外还需要控制是否要在 Component 挂载后立刻触发,因此还要一个 `immediate` 参数。
于是就可以得到我们的 Props Spec
```jsx
const PropsSpec = Type.Object({
time: Type.Number(),
content: Type.String(),
immediate: Type.Boolean(),
});
```
> Trait 的 Props Spec 也是通过 TypeBox 定义的。
#### Trait State Spec
由于 Timer Trait 还需要暴露状态,所以我们还需要添加 State Spec ,来定义 Timer Trait 暴露的 State 以及其类型。
```jsx
const StateSpec = Type.Object({
status: Type.KeyOf(
Type.Object({
waiting: Type.Boolean(),
finished: Type.Boolean(),
stopped: Type.Boolean(),
})
),
});
```
#### Trait Methods Spec
我们还需要提供 Method 给外部进行调用,来开启或者清除定时器,所以还需要对 Method 进行定义。这里我们提供 `start``stop` 两个 Method。
```jsx
const methods = [
{
name: 'start',
parameters: Type.Object({}),
},
{
name: 'clear',
parameters: Type.Object({}),
},
];
```
### Trait Factory & Implementation
在定义完成完整的 Trait Spec 之后,我们就可以开始着手实现 Timer Trait 的逻辑了。首先要实现一个 Trait 的工厂函数,其返回真正 Trait 的 Implementation。
在工厂函数中我们可以声明一些变量来存储数据,比如 Timer Trait 这里就需要创建一个 Map 来记录每个 Component 对应的 Timer ,以便用来清除计时器。
> 由于 Trait 本质是一个纯函数,无法拥有状态,所以 Trait 需要工厂函数。工厂函数提供了一个闭包,用来给 Trait 存储状态。要注意的是,对于同一种 Trait所有的 Trait Implementation 实例,共用的是同一个闭包。
下面我们来看一下完整的实现代码:
```jsx
const TimerTraitFactory = () => {
const map = new Map();
return props => {
const {
time,
content,
immediate,
services,
componentId,
subscribeMethods,
mergeState,
} = props;
// 实现 clear method
const clear = () => {
const timer = map.get(componentId);
if (timer) {
clearTimeout(timer);
mergeState({
status: 'stopped',
});
map.delete(componentId);
}
};
// 实现 start method
const start = () => {
clear();
const timer = setTimeout(() => {
// 计时器结束后,弹出 alert
alert(content);
mergeState({
status: 'finished',
});
}, time || 0);
mergeState({
status: 'waiting',
});
map.set(componentId, timer);
};
// 注册 Method 到组件中
subscribeMethods({
start,
clear,
});
// 返回 Trait Result
// 使用 componentDidMount 和 componentDidUnmount 生命周期来开启和清除计时器
return {
props: {
// 在 Component 挂在后启动计时器
componentDidMount: [
() => {
if (immediate) {
start();
}
},
],
componentDidUnmount: [
() => {
clear();
},
],
},
};
};
};
```
### Create Trait
最后,通过 `runtime` 提供的 `implementRuntimeTrait` 来生成最终的 Trait 。至此,完整的 Timer Trait 的实现就到这里完成啦。
```jsx
export default implementRuntimeTrait({
version: 'custom/v1',
metadata: {
name: 'timer',
description: 'Create a timer to alert, and could be clear.',
},
spec: {
properties: PropsSpec,
state: StateSpec,
methods,
},
})(TimerTraitFactory);
```
## Trait API 文档
Trait Spec 和 Component 基本上是一样的,仅有些许不同。
| 参数名 | 类型 | 说明 |
| -------- | --------- | -------------------------------------- |
| version | string | |
| kind | `"Trait"` | 固定不变,表示这是一个 Trait 的 Spec。 |
| metadata | | 详见下文 |
| spec | | 详见下文 |
### Trait Spec 的 Metadata
Trait Spec 的 metadata 内容比 Component 少一些。
| 参数名 | 类型 | 备注 |
| ----------- | ------------------- | ------------ |
| name | string | Trait 的名称 |
| annotations | Record<string, any> | |
### Trait Spec 的 Spec 字段
| 参数名 | 类型 | 备注 |
| ---------- | ------------------------------- | ---- |
| properties | JSONSchema | |
| state | JSONSchema | |
| methods | Record<KMethodName, JSONSchema> | |
### Trait Implementation 的参数
Trait Implementation 接受的参数和 Component 几乎完全相同,可以参考 Component 的相关章节。
### TraitResult
TraitResult 是 Trait 最重要的部分,它是 Trait 函数的返回结果,将会被直接传给 Component 作为参数。更多具体内容,可以参考 Component Implementation 的参数的相关章节。
TraitResult 是一个 object属性如下
| 参数名 | 类型 | 是否必须 | 说明 |
| ------------------- | ------------------------ | -------- | ---------------------------------------------------------------- |
| customStyle | Record<string, string> | 否 | 传递给 Component 的样式 Map。object 的 value 应该是 CSS 字符串。 |
| callbackMap | Record<string, Function> | 否 | Component 的回调函数 Map。主要用于 Event Trait。 |
| componentDidUnmount | Function[] | 否 | 生命周期钩子。将会在 Component 卸载后执行。 |
| componentDidMount | Function[] | 否 | 生命周期钩子。将会在 Component 挂载后执行。 |
| componentDidUpdate | Function[] | 否 | 生命周期钩子。将会在 Component 更新后执行。 |