背景和动机
- http://projectmanage.netease-official.lcap.163yun.com/dashboard/FeatureDetail?id=2655876085800704&id1=null&versionid=null
- https://docs.popo.netease.com/lingxi/9cf774bd147b4527affb135761d33461?header=false%2Cfalse#0EoP-1686145869247
- 前端组件属性要支持类型检查
- 前端组件事件缺乏推导和传递
- 方法的类型有 bug
- 前端组件 YAML,特别是让用户开发扩展组件缺乏清晰的规范
整体设计
上次已经决定了:
- 采用 d.ts 约束组件声明(因为类型表示、表达式的依赖关系等比较复杂)
- 最好跟 NASL Language Server 中的定义一致(减少类型检查的转换成本)
- 优先用 decorator(可以做检查和约束)
- 枚举暂时用 string(因为前端选项中枚举的情况太多了)
本提案为了规范 d.ts 的书写写法,参考了以下内容:
- nasl.ui.definition.ts 的已有实现
- vue-property-decorator
- vue-facing-decorator
- vant 的 createComponent 的设计
- vant 生成的 dts
在调研中进一步发现的一些问题
- 后来发现 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
有没有兼容性问题
要考虑组件升级时的一些问题。
结论
由评审人员最后补充。