Typescript如何根据泛型返回不同的类型

1,796 阅读3分钟

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之类的一把梭。稍微复杂一点点就呆住了。

更多的是记录一下这种写法,而不是思路,思路很简单。

如果有更好的方案欢迎大佬们提出。