Typescript如何根据泛型返回不同的类型
背景
在做一个自定义表单,需要把Json解析出来,然后生成对应的表单。表单会有input、select、number-input、radio之类的,对于input、number-input这种没什么问题。但是,对于Radio、Select这种,还需要有它对应的key/value,前端需要根据这个key/value来进行渲染option。
项目中使用了TS,开始做的时候就在想这个TS要如何申明。期望能达成CommonType<’radio’>、CommonType<’input’> 返回公共属性+自有属性的联合类型。
方案
方案一
利用extends 和三目运算
// form item的类型
type Type = 'input'|'number-input'|'radio'|'select';// 根据需要自由扩展
// 公共属性
type CommonType<T extends Type> = {
id: string;
key: string;
label: string;
type: T;
}
// 实际属性
type RealType<T extends Type> = CommonType<T> & (
T extends 'radio' ?
{
values: {label: string; value: string;}[];
} : {}
)
// 验证
const test: RealType<'radio'> = {
id: '',
key: '',
label: '',
type: 'radio', // 这里如果不写radio ts校验会报错 radio1 assignable radio
values: [], // 如果values不写或者不是对应的数组类型 ts校验也会报错 缺少values
};
这样子就可以实现,根据泛型返回同步的实际类型,基本满足的需求。但是三目运算符有个致命问题,就是如果需要对每一个type都进行处理,就要疯狂嵌套,太过离谱。
想了半天不知道怎么处理,于是就跑去请教大佬。
方案二
大佬给出了一个方案,之前我也想过这个思路,只是没想到Typescript真的可以这么写。就是定义个Type Map(自创的,非官方名称)。
type CustomType = {
radio: {
values: {label: string; value: string;}[];
},
select: {
options: {key: string; value: string}[];
},
};
// form item类型 如果有自有属性的就在上面声明,没有就直接在前面申明
type Type = 'input'|'number-input'| keyof CustomType;
// 公共类型
interface CommonType<T extends Type> {
id: string;
key: string;
label: string;
type: T;
}
// 实际类型
type RealType<T extends Type> = T extends keyof CustomType ? CustomType[T] & CommonType<T> : CommonType<T>;
// Radio的数据类型
type RadioType = RealType<'radio'>;
// select的数据类型
type SelectType = RealType<'select'>;
const test: RadioType = {
id: '',
key: '',
label: '',
type: 'radio', // 这里如果不写radio ts校验会报错 radio1 assignable radio
values: [
{ // 这里如果乱声明也会报错
label: '',
value: '',
},
], // 如果values不写或者不是对应的数组类型 ts校验也会报错 缺少values
};
const test1: SelectType = {
id: '',
key: '',
label: '',
type: 'select', // 这里如果不写select ts校验会报错 'radio' assignable 'select'
options: [
{
key: '',
value: '',
},
], // 如果options不写或者不是对应的数组类型 ts校验也会报错 缺少option
};
这样就可以相对方案一更优雅的申明各种类型的属性了。
总结
其实这两个方案本身是没有什么高级可言的,核心思路就是泛型判断+联合类型。对于我来讲主要的问题是Typescript的语法,比如方案二这种写法我最开始就希望能这么写,但是完全不知道语法是否合规(其实按JS 对象的写法就可以,但是没有尝试,下意识觉得不行),大佬写给我的时候我打呼还能这样写。再比如第一个方案,我也是看了ReduxToolKit里的TS才知道可以使用extends + 三目运算符。日常定义ts的时候都是interface/type + 泛型 + Record之类的一把梭。稍微复杂一点点就呆住了。
更多的是记录一下这种写法,而不是思路,思路很简单。
如果有更好的方案欢迎大佬们提出。