logo

鱼肚的博客

Don't Repeat Yourself

Typescript中的类型联动

问题说明

类型联动,指的是一个对象中,A属性的值变化,会引起B属性的类型变化。

假设有这样一个类型:

1type Component = {
2  type: 'button' | 'input' | 'select' | 'textarea';
3  payload: {
4    onChange: Function;
5    onClick: Function;
6    type: string;
7    placeholder: string;
8  };
9}

类型联动,指的是当 type 的值发生变化时,payload的类型也跟着变化,比如当type为'input'时,可能希望payload中包含onChange,而当type为'button'时,则不希望payload中包含onChange。

粗糙的解决方案

最粗糙的解决方案,是用 Union 的形式,将多种类型组合起来。

类似下面的形式:

1type ButtonComponent = {
2  type: 'button';
3  payload: {
4    onClick: Function;
5    type: string;
6  }
7}
8
9type InputComponent = {
10  type: 'input';
11  payload: {
12    onChange: Function;
13    placeholder: string;
14  }
15}
16
17// ... 将所有可能的类型写一遍
18
19// 最后,给一个union类型
20type Component = ButtonComponent | InputComponent | ... | OtherComponents
21// => {type: 'button', payload: {onClick: Function, type: string}} | {type: 'input', payload: {onChange: Function, placeholder: string}}

这种方法比较笨拙,因为它需要把每种可能的组合都写一遍。

手写类型的时候,还可以勉强用一下,当遇到需要类型推导的场景时,就不好使了。

类型推导

使用如下的代码,可以推导出我们想要的类型

1// 先定义一个类型映射关系
2type ComponentPayload = {
3  button: {
4    onClick: Function;
5    type: string;
6  }
7
8  input: {
9    onChange: Function;
10    placeholder: string;
11  }
12
13  // 其它略过不写
14}
15
16// 使用 keyof 获取到所有的 key
17type ComponentPayloadKeys = keyof ComponentPayload;
18// => 'button' | 'input'
19
20// 再结合类型推导,构造出valueof
21type ComponentPayloadValues = ComponentPayload[keyof ComponentPayload]
22// => { onClick: Function; type: string; } | { onChange: Function; placeholder: string; }
23
24// 如果先构造出 {type => {type, payload}} 的格式,就可以用上面的valueof的方式,获取到目标类型
25type Component = {
26  [T in keyof ComponentPayload]: {
27    type: T;
28    payload: ComponentPayload[T]
29  }
30}[keyof ComponentPayload]
31// => {type: 'button', payload: {onClick: Function, type: string}} | {type: 'input', payload: {onChange: Function, placeholder: string}}

至此,就大功告成了。

总结

TS中的类型跟随值变化,本质是还是类型跟随类型变化,只是TS中允许把值作为类型的一种。

通过keyof、Generic等操作生成Union类型,可以实现类型之间的联动。