TypeScript类型体操挑战(二十五)

176 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情

困难

Union to Intersection(联合转交叉)

挑战要求
在线示例

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

// 结果:'foo' & 42 & true
type I = Union2Intersection<'foo' | 42 | true>

答案参考自解答区👍比较多的


当使用infer推断会发生逆变的类型中时,则推断结果会得到一个交叉类型

在线示例

type Bar<T> = 
  T extends { a: (x: infer U) => void; b: (x: infer U) => void }
    ? U
  : never;

// string
type T1 = Bar<{ a: (x: string) => void; b: (x: string) => void }>; 
// string & number
type T2 = Bar<{ a: (x: string) => void; b: (x: number) => void }>; 

官方文档中有infer对应的说明。


所以代入到上面的解答与示例中可以这么去看:

注意:联合类型作用于泛型时会发生分布行为

type UnionToIntersection<'foo' | 42 | true> = 
  ((arg: 'foo') => any | (arg: 42) => any | (arg: true) => any) extends (inferArg: infer I) => void
    ? I 
  : never

也就符合了上面说的infer推断情况,因为inferArg的类型存在逆变,所以该类型必须是'foo' & 42 & true,这样才能满足。但是怎么可能有类型是'foo' & 42 & true呢,所以最后会得到never

Get Required(保留所有必填字段)

挑战要求
在线示例

type GetRequired<T> = {
  [P in keyof T as T[P] extends Required<T>[P] ? P : never]: T[P]
}


// 使用示例:expected to be { foo: undefined }
type I = GetRequired<{ foo: undefined; bar?: undefined }>

答案参考自解答区


上面用到了工具类型Required,源码如下:

type Required<T> = { [P in keyof T]-?: T[P]; }

首先使用到了 as 进行键的重新映射,然后通过Required将对象的属性变为必选的,也就是通过-?来进行处理,这样TS就能准确地识别了。

可选属性的值可不是单单多了个undefined类型而已,要不然上面的示例都没法通过了。

Get Optional(保留所有可选字段)

挑战要求
在线示例

type GetOptional<T> = {
  [P in keyof T as T[P] extends Required<T>[P] ? never : P]: T[P]
}


// 使用示例:expected to be { bar?: string }
type I = GetOptional<{ foo: number, bar?: string }>

一开始我还想用Partial来进行处理:

type GetOptional<T> = {
  [P in keyof T as T[P] extends Partial<T>[P] ? P : never]: T[P]
}

发现行不通,所以还是得用Required来进行处理。

其实就是跟上面的Get Required的处理相反,只是这次用来排除掉必选字段而已。

所以处理步骤就是:

  1. 使用 as 键的重新映射
  2. 判断当前键对应的值类型是不是必选字段
    • 是则返回never
    • 否则保留当前键