TypeScript 泛型编程:进阶

171 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 4 天

如何使用映射类型 in

type Person = {id?: number, name: string, age: number}

type Readonly<T> = {
  readonly [K in keyof T]: T[K]
}
type X1 = Readonly<Person>

// 重名了,谁离我近我就用哪个,这里叫做覆盖,如果我写了 TS 默认写的那个就没有用了
type Partial<T> = {
  [K in keyof T] ?: T[K]
}
// 假设 Person 有个 id, 有时候我们做不了 id
// 很多时候在做表单创建的时候,id 做不了,等到我保存成功才有
type X2 = Partial<Person>

// -? 把 id? 变成了必须选的
type Required<T> = {
  [k in keyof T]-?: T[k]
} 
type X3 = Required<Person>

// 以下 O 和 O2 等价
type O =  {
  [index: string]: number
}
type O2 = Record<string, number>

type Record<Key extends string | number | symbol, Value> = {
  [k in  Key]: Value
}
type X4 = Record<string, number>

Screen Shot 2022-10-12 at 10.40.41 PM.png

这里为什么会出现 string | number

// 1. 因为 JS 程序员说 我的 key 可以是 number, 所以这个 冒号 : 有做特殊处理
type Y = {
  // 4. 所以这里没有 一一对应
  /* 5. 所以这个冒号在用泛型的时候,就会遇到一些问题,
  本来是一个简单的 string 类型,它会变成一个并集,
  那你变成一个并集之后,是会走分配律的,
  就导致整个 TS 的类型可能会遇到一些不能预测的 bug */
  [k: string]: number
}    
// 6. 所以需要发明一种新的语法
type Z = {
  [k in string]: number
}

type YK = keyof Y // string | number

type ZK = keyof Z // srting
const obj = {
  1 : 'one',
  2 : 'two'
}

const n = 1
obj[n]
// 2. 所以对于 JS 程序员来说这个下标就可以是 number,虽然根本不存在于有一个 number 的 number 下标

// 3. TS 程序员为了兼容 JS 的习惯,你说你是 string 的时候,我觉得你可能是 number | string
// 因为分配律,这里是 A 里面的每一个元素是否包含于 b
// 如果在里面我就不要
type Exclude<A, B> = A extends B ? never : A
type X5 = Exclude<1 | 2 | 3, 1 | 2> // 3
// 使用代入法推演过程
type X5 = 1 | 2 | 3 extends 1 | 2 ? never : 3
type X5 = 
| 1 extends 1 | 2 ? never : 1  // never
| 2 extends 1 | 2 ? never : 2 // never
| 3 extends 1 | 2 ? never : 3 // 3
// type X5 = never | never | 3
type X5 = 3

Screen Shot 2022-10-13 at 6.46.47 PM.png

// A 里面的每一个元素是否包含于 B , 如果在里面我就要
type Extract<A, B> = A extends B ? A : never
type X6 = Extract<1 | 2 | 3, 2 | 4> // 2

Screen Shot 2022-10-13 at 7.05.09 PM.png

type Person = {id?: number, name: string, age: number}
// Omit 的意思是 我需要忽略掉这个对象的两个 key
type Omit<T, Key> = {
  [k in keyof T as k extends Key ? never : k] : T
}

type X7 = Omit<Person, 'name' | 'age'>

这里的 as 可以强制断言,改写你的值,就很像赋值。 Screen Shot 2022-10-13 at 7.40.02 PM.png

Screen Shot 2022-10-13 at 8.00.52 PM.png

看一哈 TS 内置的 Omit

Screen Shot 2022-10-13 at 8.06.34 PM.png

使用到了 Pick

Screen Shot 2022-10-13 at 8.07.56 PM.png

它是一个映射类型

// 模拟一下 Pick
type Person = {id?: number, name: string, age: number}
type Pick<T, Key extends keyof T> = {
  [k in Key]: T[Key]
}
type X8 = Pick<Person, 'name' | 'age'>

Screen Shot 2022-10-13 at 8.13.49 PM.png X8 正好获取到 name、age 不要 id

说明 Pick 映射到 key 是后面的集合。

有了 Pick 在写 Omit

type Person = {id?: number, name: string, age: number}
type Exclude<A, B> = A extends B ? never : A
type X5 = Exclude<1 | 2 | 3, 1 | 2>

type Omit<T, Key extends keyof T> = Pick<T, Exclude<keyof T, Key>>
type X7 = Omit<Person, 'name' | 'age'>

type Pick<T, Key extends keyof T> = {
  [k in Key]: T[Key]
}
type X8 = Pick<Person, 'name' | 'age'>

Screen Shot 2022-10-13 at 8.21.37 PM.png

如何使用 -readonly

readonly 是把一个对象的属性变成可读的,那么如何把一个对象的属性变成可写的。

// 这里该如何填?
type Mutable<Type> = {
  ?????? [Property in keyof Type]: Type[Property]
}
type Y = Mutable<Readonly<Person>>  // 如何用 Mutable 变成全可写的?
type Person = {
  readonly id: number
  readonly name: string
  readonly age: number
}


// 使用 -readonly 把可读的变成可写的
type Mutable<T> = {
  -readonly [K in keyof T]: T[K]
}

type X9 = Mutable<Person> 

Screen Shot 2022-10-13 at 9.02.49 PM.png 就全都没有 readonly

总结

从类型中创造类型:

  • extends 包含于
  • keyof 获取到一个对象的所有 key,把它 key 变成一个并集
  • T['name'] T 是一个类型,然后它的下标是可以直接传一个字符串,如果是一个泛型可以传一个 key,这个 key 就必须是 keyof key。 [K in keyof T]
  • Mapped Types 映射类型
    • in keyof
    • - readonly[]
    • [] -
      • -readonly [K in keyof T] -?: [K] 前面能减 readonly, 后面 []- 表示可选
    • as 断言

如何学习泛型

  • 别用 any
  • 做题:🔗
  • 与别人交流

如下图所示如何做题和解答 Screen Shot 2022-10-13 at 9.25.43 PM.png

文章分享: