ts类型体操

记录一些高级ts推导

toMap

背景: 某日在公司ts体操群,看到有同学在问,题目看着也不是很复杂,也有同学迅速给出了答案,然后我就瞎了, 明明都看的懂,咋组合在一起就看不明白了


declare function toMap(params): {}

const data = toMap([
  { key: 'name', value: 'zhao大建'}, 
  { key: 'age', value: 18}
])

// 希望实现toMap的函数声明使得data具有{name: 'zhao大建', age: 18}的类型推导

1
2
3
4
5
6
7
8
9
10

先附上答案, typescript playGroundopen in new window

type UnionToIntersection<U> = (
  U extends any ? (k: U) => void: never
) extends ((k: infer I) => void) 
  ? I
  : never;

declare function toMap<
  K extends string,
  V extends string | number,
  T extends { key: K, value: V}[],
  R = T[number]
>(params: T): UnionToIntersection<R extends T[number] ? {[P in R['key']]: R['value']} : never>;

const data = toMap([
  { key: 'name', value: 'zhao大建'}, 
  { key: 'age', value: 18}
])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

泛型运用较多,一步步拆分细看

declare function toMap<
  K extends string,
  V extends string | number,
  T extends { key: K, value: V}[],
  R = T[number]
>(params: T): R extends T[number] ? {[P in R['key']]: R['value']} : never;
1
2
3
4
5
6

K V T泛型都是约束传入的格式,没啥好多说的,都能通过传入的params来反推出具体的类型,那这个R = T[number]是个什么东西,万能google后可知T[number]可获取T数组的元素联合类型,这样我们就得到了如下的一个联合类型

type A = {name: 'zhao大建'} | {age: 18}
1

重头戏,再来看UnionToIntersection这个类型,听名字就是把联合类型转化成交叉类型,(A | B => A & B), 具体来看 U extends any恒成立,那就是 (k: U) => void extends ((k: infer I) => void) ? I : never, 这里涉及到一些逆变和协变的知识, 可以参照下mpx的一篇文章open in new window掘金上的一篇文章open in new window, 最开始我百思不得其解,这个infer我知道啊,在ReturnType上见过

附上代码

type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : any
type fnType = MyReturnType<() => string[]>
1
2

那推导出来的I不就是U么,这感觉都没啥意义, 注意,我们这里传入的U是联合类型,如果传入的是其他类型还真没啥意义

如果这个U是X|Y|Z的联合类型,

其实不是这么推导

(((k: X|Y|Z) => void) extends ((k: infer I) => void) ? I : never)
1

其实他是这么推导的

| (((k: X) => void) extends ((k: infer I) => void) ? I : never)
| (((k: Y) => void) extends ((k: infer I) => void) ? I : never)
| (((k: Z) => void) extends ((k: infer I) => void) ? I : never)
1
2
3

那这个I既是X又是Y又是Z, 那就是X&Y&Z的类型,同理UnionToIntersection<string | number>我们会直接得到一个never类型,因为string & number不存在,但{name: 'zhao大建'} & {age: 18}是存在的,即{name: 'zhao大建', age: 18}

Last Updated:
Contributors: 赵仁建