Skip to main content

完善前端组件 ts 的定义

· 29 min read
赵雨森

背景和动机

整体设计

上次已经决定了:

  • 采用 d.ts 约束组件声明(因为类型表示、表达式的依赖关系等比较复杂)
  • 最好跟 NASL Language Server 中的定义一致(减少类型检查的转换成本)
  • 优先用 decorator(可以做检查和约束)
  • 枚举暂时用 string(因为前端选项中枚举的情况太多了)

本提案为了规范 d.ts 的书写写法,参考了以下内容:

在调研中进一步发现的一些问题

  • 后来发现 d.ts 文件无法使用 decorator,只能用 ts 文件写
  • decorator 只能在 class 中使用,比较局限:会导致所有函数等都要在 class 中写

举例

比如 USelect 组件最复杂,以它为例:

属性类型依赖:((pagination => dataSource => valueField), multiple, converter) => value, ...

其它属性举例:

  • textField
  • pageSize
  • sorting
  • description
  • descriptionField
  • iconField
  • filterable
  • ...

引入声明包:

TypeScript 中平级报错会出问题:

方案一:用高阶函数,有几层依赖就弄成几阶函数

  • const createUSelect = (pagination) => (dataSource) => (valueField, multiple, converter) => USelect
  • const createUCarousel = (dataSource) => (valueField, multiple, converter) => UCarousel

缺点

  • 可能导致函数层级有差异,不太好统一,引入其它判断的复杂性
  • 普通函数无法添加 decorator

方案二:用一阶函数

差不多。没有明显的优势。

方案三(推荐):用一些技巧

完整举例

  • 组件属性(包含暴露的属性)
  • 组件事件
  • 组件逻辑(方法)
  • 组件插槽

事件相关设计

示例

export class ChangeItemEvent {
selected: nasl.core.String;
value: nasl.core.String;
oldValue: nasl.core.String;
item: nasl.core.String;
oldItem: nasl.core.String;
label: nasl.core.String;
}
export class ChangeItemsEvent {
selected: nasl.core.Boolean;
item: nasl.core.String;
value: nasl.collection.List<nasl.core.String>;
oldValue: nasl.collection.List<nasl.core.String>;
items: nasl.collection.List<nasl.core.String>;
oldItems: nasl.collection.List<nasl.core.String>;
}

@Event({
title: '选择前',
description: '选择某一项前触发。',
})
private onBeforeSelect(event: nasl.ui.ChangeItemEvent): void {}

@Event({
title: '选择时',
description: '选择某一项时触发',
})
onInput(event: String): void {}

@Event({
title: '选择后',
description: '选择某一项后触发。单选模式中:',
})
onSelect(event: nasl.ui.ChangeItemEvent): void {}

@Event({
title: '改变后',
description: '选择值改变时触发。单选模式中:',
})
onChange(event: nasl.ui.ChangeItemEvent): void {}

规范设计

  • 时和后,不要同时出现。比如选择框的选择时和选择后。
  • 单多选切换时,value 和 values 不同时出现,与组件的属性 value 一致。
  • 为了简化设计 pagination、multiple、converter 等前置属性切换不支持动态绑定。

问题整改

  • BaseEvent 和 EventTarget 合并。

方案一:继续在当前具名的类型上整改

export class ChangeItemEvent<T, V, M> {
selected: nasl.core.Boolean;
value: nasl.core.String;
oldValue: nasl.core.String;
item: nasl.core.String;
oldItem: nasl.core.String;
label: nasl.core.String;
}
export class ChangeItemsEvent<T, V, M> {
selected: nasl.core.Boolean;
item: nasl.core.String;
value: nasl.collection.List<nasl.core.String>;
oldValue: nasl.collection.List<nasl.core.String>;
items: nasl.collection.List<nasl.core.String>;
oldItems: nasl.collection.List<nasl.core.String>;
}

@Event({
title: '选择前',
description: '选择某一项前触发。',
})
private onBeforeSelect(event: nasl.ui.ChangeItemEvent<T, V, M>): void {}

@Event({
title: '选择时',
description: '选择某一项时触发',
})
onInput(event: String): void {}

@Event({
title: '选择后',
description: '选择某一项后触发。单选模式中:',
})
onSelect(event: nasl.ui.ChangeItemEvent<T, V, M>): void {}

@Event({
title: '改变后',
description: '选择值改变时触发。单选模式中:',
})
onChange(event: nasl.ui.ChangeItemEvent<T, V, M>): void {}

方案二:事件类型匿名化

@Event({
title: '选择前',
description: '选择某一项前触发。',
})
private onBeforeSelect(event: {
selected: nasl.core.Boolean;
value: M extends true ? (C extends '' ? nasl.collection.List<V> : nasl.core.String) : V;
oldValue: M extends true ? (C extends '' ? nasl.collection.List<V> : nasl.core.String) : V;
item: T;
oldItem: T;
}): void {}

@Event({
title: '选择时',
description: '选择某一项时触发',
})
onInput(event: String): void {}

@Event({
title: '选择后',
description: '选择某一项后触发。单选模式中:',
})
onSelect(event: {
selected: nasl.core.Boolean;
value: M extends true ? (C extends '' ? nasl.collection.List<V> : nasl.core.String) : V;
oldValue: M extends true ? (C extends '' ? nasl.collection.List<V> : nasl.core.String) : V;
item: M extends true ? nasl.collection.List<T> : T;
oldItem: M extends true ? nasl.collection.List<T> : T;
}): void {}

@Event({
title: '改变后',
description: '选择值改变时触发。单选模式中:',
})
onChange(event: {
value: M extends true ? (C extends '' ? nasl.collection.List<V> : nasl.core.String) : V;
oldValue: M extends true ? (C extends '' ? nasl.collection.List<V> : nasl.core.String) : V;
item: M extends true ? nasl.collection.List<T> : T;
oldItem: M extends true ? nasl.collection.List<T> : T;
}): void {}

需求点

  • 所有组件属性本身能够进行类型检查
  • 可访问的组件属性的类型能够合理地推导出来
  • 组件事件的字段类型能够通过泛型传递
  • 组件方法的返回类型修复(这个可放二期)

具体工作

1. 【前置】整理所有组件的类型定义(最好 +2 位前端 PC/H5)

  • 优化 YAML,梳理出组件的类型依赖关系和泛型定义
  • 修复类型一些错误
  • 优化事件类型
  • 优化暴露出来的属性

2. TS 解析成组件的 JSON 定义

3. IDE 适配开发,包括 LS 的一些调整

4. TS 定义生成 Excel 方便 Review

有没有兼容性问题

要考虑组件升级时的一些问题。

结论

由评审人员最后补充。