美文网首页
TypeScript全解:泛型编程(上)

TypeScript全解:泛型编程(上)

作者: littleyu | 来源:发表于2023-06-12 22:00 被阅读0次

    什么是泛型?
    泛,指多
    简单来说就是多种类型

    只要你能看懂 JS 的函数,那么你就能看懂 TS 的泛型

    JS:

    const fn = (a, b) => a + b
    const result = fn(1,2) // 3
    

    TS:

    type Fn<A, B> = A | B
    type Result = Fn<string, number> // string | number
    

    很像把,格式上看起来一模一样,非常简单

    思考:函数的本质是什么?

    不知道大家平时在写函数的时候,有没有想过这个问题?

    • 复用代码?那我声明就了一次,也没复用啊
    • 抽离逻辑?为了代码更好看?
    • 输入输出?这些目的是什么?不用函数我也能输入输出

    个人认为:函数的本质是退后执行的、部分待定的代码

    console.log(1)
    

    当这部分代码被解析的时候, console 就已经执行了,那我们想晚一点执行呢?用函数

    const fn = () => console.log(1)
    setTimeout(() => fn(), 2500)
    

    所以函数的本质之一就是:在我们这种面相过程的编程里面,把执行时机往后推迟

    再继续看,还是这段代码,这里的 fn 函数体非常死板,只能打印出 1,于是我们改造:

    const fn = (x) => console.log(x)
    
    fn(222)
    fn(333)
    

    所以函数的第二个特点就是部分待定的代码,其实还是往后推迟,这里的 console 我不想立马确定,我希望能想什么时候执行就什么时候执行,想 log 几就 log 几

    举一反三:泛型的本质

    泛型的本质是退后执行的、部分待定的类型

    实例

    function echo(n: number | string | boolean) {
      return n
    }
    

    实际上我们希望这个函数的类型是传入什么类型就返回什么类型

    • echo(n: number) => number
    • echo(n: string) => string
    • echo(n: boolean) => boolean

    很遗憾,如果不用泛型做不到,即使你函数里面加了判断

    function echo(n: number | string | boolean) {
      if (typeof n === 'number'){
        return n
      } else if (typeof a === 'string') {
        return n
      } else {
        return n
      }
    }
    

    依然存在错乱的情况

    没有泛型,有些奇怪的需求就无法满足
    没有泛型的类型系统,就如同 JS 没有函数

    泛型-简单难度

    type Union<A, B> = A | B
    type Intersect<A, B> = A & B
    
    type List<T> = { // 就像函数一样
      [key: string]: T
    }
    
    type Person = { name: string; age: number; }
    List<Person> // 能匹配下面这些数据 
    {
      a: Person,
      b: Person,
      ...
    }
    

    默认值

    既然函数可以有默认值,泛型能有么?当然有

    type List<T = string> = {
      [key: string]: T
    }
    

    泛型-中等难度

    来看一道题目

    type Person = { name: string; }
    
    type LikeString<T> = ??? // 如果这个 T 是 string,则返回 true,否则 false
    type LikeNumber<T> = ??? //如果这个 T 是 number,则返回 true,否则 false 
    type LikePerson<T> = ??? // 如果这个 T 是 Person,则返回 true,否则 false
    
    type R1 = LikeString<'hi'> // true
    type R2 = LikeString<true> // false
    type S1 = LikeNumber<666> // 1
    type S2 = LikeNumber<false> // 2
    type T1 = LikePerson<{ name: 'hi', xxx: 1 }> // yes
    type T2 = LikePerson<{ xxx: 1 }> // no
    

    我们先想一下如果在 JS 中怎么实现

    const likeString = a => typeof a === 'string' ? true : false
    

    那么在 TS 中怎么实现呢?

    type LikeString<T> = T extends string ? true : false
    

    疑惑点来了,为何 TS 中的判断要用 extends,为什么不能像 JS 那样使用 ===,多有语义化

    其实这跟类型兼容、父类型、子类型有关系,在类型中很多时候我们做不到完全一致,因为集合与集合之间相等的时候是很少的,大多时候是从属关系,所以我们把这里的 extends 我更建议读作包含于

    剩下的实现就很简单了:

    type LikeNumber<T> = T extends number ? 1 : 2
    type LikePerson<T> = T extends Person ? 'yes' : 'no'
    

    其中会有这几条规则

    1. 若 T 为 never,则表达式的值为 never
    2. 若 T 为联合类型,则分开计算

    第一条规则:

    type R1 = LikeString<never> // never
    

    违背常理的事情又发生了,三元表达式竟然多计算出了一种结果!竟然返回了 never!

    第二条规则:

    type ToArray<T> = T extends unknown ? T[] : never
    
    type Result = toArray<string | number>
    
    思考:此时 Result 的类型是什么
    1. Result 为 (string | number)[]
    2. Result 为 string[] | number[]

    答案是 2,为 string[] | number[],即为:

    type Result = toArray<string | number>
    
    type Result = string extends unknown ? string[] : never
                   |
                  number extends unknown ? number[] : never
    

    为什么 TS 会这么推导呢?一开始我也很不理解,直至后来我用函数来理解

    伪代码:

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

    实际上我们经常会写出这样的函数,根据 a 的类型来判断返回值的类型,所以 TS 的预判是正确的

    由此可推,上面返回的 never 也是合理的:

    function fn(a: T): ToArray<T> {
      if (type a === 'string') {
        return string[] 
      } else if (typeof a === 'number') {
        return number[]
      } else {
        return never // TODO: 注意看这里,你传了一个 never 进来,自然而然就是返回 never
      }
    }
    

    注意上述两条规则只对泛型有效(上面的代码也一直在用函数的思路来举例)

    type X = never extends unknown ? 1 : 2 // 1
    

    在加大一点难度

    获取对象的 key

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

    判断两个类型是否相等

    type Eq<A, B> = A extends B ? B extends A ? true : false : false
    

    思路就是判断 A 是否包含于 BB 是否包含于 A,那么为 true,否则为 false

    很遗憾,不完全对,看起来很正确,其实因为 分开计算 的规则下,得到的并不是这样的结果

    获取对象的 value

    type Person = { name: string; age: number; }
    type GetKeyType<T, K extends keyof T> = T[K]
    
    type Result = GetKeyType<Person, 'name'> // string
    

    相关文章

      网友评论

          本文标题:TypeScript全解:泛型编程(上)

          本文链接:https://www.haomeiwen.com/subject/vjwzedtx.html