泛型编程

46 阅读2分钟

1. 入门

1.1 泛型就像是函数

// Generic Types
const f = (a, b) => a + b
const result = f(1, 2)

type F<A, B> = A | B
type Result = F<string, number>

/**
 * 函数的本质
 * 推后执行的、部分待定的代码
 */

console.log(1)

// 推后执行的
const f1 = () => console.log(1)
setTimeout(() => {
  f1()
}, 3000)

// 部分待定的
const f2 = (x) => console.log(x)
f2(2222)

const f3 = (fn) => fn(1)
f3(console.log)

const f4 = (fn, x) => fn(x)
f4(console.log, 1)

1.2 为什么会有泛型

/**
 * 泛型的本质
 * 推后执行的、部分代码的类型
 */
// function echo(n: number | string | boolean) {
//   return n
// }

function echo(whatever: number | string | boolean) {
  switch (typeof whatever) {
    case 'number':
      return whatever
    case 'string':
      return whatever
    case 'boolean':
      return whatever
  }
}

1.3 难度为一星的泛型

interface List<A> {
  [index: number]: A
}

// 代入法
// type X = List<string>
// 此时X就等价于下面
interface X {
  [index: number]: string
}

// 默认值
interface Hash<V = string> {
  [key: string]: V
}

type Person = {
  name: string
}

type v = Hash
type v1 = Hash<Person>

1.4 在泛型中使用extends

/**
 * 规则
 * 1. 若T为never,则表达式的值为never
 * 2. 若T为联合类型,则分开计算 见以下 ToArray
 */

/**
 * 只对泛型有效
 * 1. 类似于乘法中的零 0 X C = 0
 * 2. 类似于乘法中的分配率 (A + B) X C = A X C + B X C
 */

type Person = { name: string }

// extends 表示 T <= string
type LikeString<T> = T extends string ? true : false
type LikeNumber<T> = T extends number ? 1 : 2
type LikePerson<T> = T extends Person ? 1 : 2
type R1 = LikeString<'hi'> // true
type R2 = LikeString<true> // false
type S1 = LikeNumber<6666> //1
type S2 = LikeNumber<false> // 2
type T1 = LikePerson<{ name: 'lwz'; xxx: 1 }> // yes
type T2 = LikePerson<{ xxx: 1 }> // no

type X1 = LikeNumber<never>

type ToArray<T> = T extends unknown ? T[] : never
type Result = ToArray<string | number>
// 当传递的类型为never时, 并不会进入后面的类型判断,直接返回never
type Result1 = ToArray<never> // Result1 为 never 类型

// type Result = string | number extends unknown ? ...
// type Result = string extends unknown ? string[] : never
//                |
//               number extends unknown ? number[] : never
// 因此等价于
// type Result = string[] | number[]

/**
 * 1. <string | number>[]
 * 2. string[] | number[] √
 */

// function f(a: T): ToArray<T> {
//   if (typeof a === 'string') {
//     return string[]
//   } else if (typeof a === 'number') {
//     return number[]
//   }
// }

1.5 在泛型中使用keyof

type Person = { name: string; age: number }
type GetKeys<T> = keyof T

type Result = GetKeys<Person>
// 验证方式
// const p: Result = ''

1.6 在泛型中使用extends keyof

type Person = { name: string; age: number }

type GetKeyType<T, K extends keyof T> = T[K]

type Result = GetKeyType<Person, 'name'> // 第二个参数写成 'x' 会报错
type X = Person['name']

2. 进阶

2.1 如何使用映射类型

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

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

type X2 = Partial<Person>
// type Partial<T> = {
//   [k in keyof T]?: T[k]
// }

type X3 = Required<Person>
type Required<T> = {
  // -? 表示必选
  [k in keyof T]-?: T[k]
}

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

// : 和 in 的区别
type Y = {
  [k: string]: number
}
type Z = {
  [k in string]: number
}

type YK = keyof Y
// 这种情况下k为number类型 离谱
const obj = {
  1: "abc",
  2: "cba"
}
type ZK = keyof Z

1.3 难度为3星的泛型

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 X5 = 1 | 2 | 3 extends 1 | 2 ?
// type X5 = 1 extends 1 | 2 ? never : never
// | 2 extends 1 | 2 ? never : never
// | 3 extends 1 | 2 ? never : 3

// type X5 = 3

type Extract<A, B> = A extends B ? A : never
type X6 = Extract<1 | 2 | 3, 2 | 4> // 2

// type Omit<T, Key> = {
//   [K2 in keyof T as K2 extends Key ? never : K2]: T
// }
type Omit<T, Key extends keyof T> = Pick<T, Exclude<keyof T, Key>>
type X7 = Omit<Person, "name" | "age">

type Pick<T, Keys extends keyof T> = {
  [K in Keys]: T[K]
}
type X8 = Pick<Person, "name" | "age">

1.4 如何使用readonly

type Person = {
  readonly id: number
  readonly name: string
  readonly age: number
}

type Mutable<T> = {
  -readonly [k in keyof T]: T[k]
}

type X = Mutable<Person>