005_TypeScript 第五章

descript

  • 泛型( generic ) : 先来看一下百度给出的中文解释

descript

  • 这一章我们就来学习一下什么是 TS 内的泛型

泛型

  • 废话不多说, 直接上例子

初识泛型

  • 一个函数, 需要参数是 number 数据类型, 返回值也是 number 数据类型

    1
    2
    3
    function fn(arg: number): number {
    // 代码忽略不计
    }
  • 又一个函数, 需要参数是 string 类型, 返回值也是 string 数据类型

    1
    2
    3
    function fn(arg: string): string {
    // 代码忽略不计
    }
  • 我们发现, 我们给函数的参数和返回值都进行了限制

    • 假设, 如果两段代码的业务逻辑一样
    • 我们可以不可把两个函数写成一个
    • 需求 : 我传递的是 数字, 返回值就是数字, 传递的是 字符串, 返回值就是 字符串
  • 思考过后, 我们想到可以用 或( | )

    1
    2
    3
    function fn(arg: number | string): number | string {
    // 代码忽略不计
    }
    • 看起来不错
    • 但是不能完全满足我们的需求
      • 参数 number 返回值 number
      • 参数 number 返回值 string
      • 参数 string 返回值 number
      • 参数 string 返回值 string
    • 以上四种情况都是可以的
    • 和我们预期的需求不一样
  • 我也可以写成 any 类型不就好了

    1
    2
    3
    function fn(arg: any): any {
    // 代码忽略不计
    }
    • 这样一来, 好像对参数和返回值的限制又都没有了
    • 那么 TS 的意义好像不大了
  • 难道说用了 TS 以后, 我们的代码反而不灵活了吗 ?

  • 来看下面一段代码

    1
    2
    3
    function fn<T>(arg: T): T {
    // 代码忽略不计
    }
    • 这个玩意看起来怪怪的, 感觉认识但又不完全认识
    • 这里函数名后面的 “<T>“ 表示给该函数定义了一个泛型, 就是预设了一个类型限制
    • 将来你调用这个函数的时候, 来传递一个类型对函数进行约束
    1
    2
    3
    4
    5
    6
    7
    // 用 string 去填充预设
    // 当前函数的参数必须是 string, 返回值也必须是 string
    fn<string>('hello')

    // 用 number 去填充预设
    // 当前函数的参数必须是 number, 返回值也必须是 number
    fn<number>(100)
    • 在来思考我们一开始的需求, 其实主要就是取决于调用的时候
  • 泛型, 其实就是在定义阶段不预先指定具体类型, 只是留下一个空位或者预设位置, 当你使用的时候在决定使用什么具体的数据类型填充

泛型用法

  1. 函数泛型

    • 就是利用泛型限定函数的参数和返回值

      1
      2
      3
      4
      5
      6
      function test<T>(arg: T): T {
      // ... 此处省略代码 10000 行
      return arg
      }

      test<number>(111) // 返回值是 number 类型
    • 也可以设置多个泛型标识符

      1
      2
      3
      4
      5
      6
      function test<T, U>(arg1: T, arg2: U): [U, T] {
      // ... 此处省略代码 10000 行
      return [arg2, arg1]
      }

      test<number, string>(100, 'hello world')
    • 泛型标识符可以设置一个, 也可以设置多个, 用哪个字母无所谓

  2. 接口泛型

    • 在定义接口的时候, 也可以使用泛型

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      interface User<T> {
      gender: T
      }

      const p1: User<string> = {
      gender: '男'
      }

      const p2: User<boolean> = {
      gender: true
      }
    • 这样看起来, 泛型是不是非常方便呢

    • 同样, 也可以设置一个, 也可以设置多个

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 制作一个方法的接口泛型
      interface Search {
      <T, Y>(name: T, age: Y): T
      }

      let fn: Search = function <T, Y>(name: T, id: Y): T {
      // ... 此处省略代码 10000 行
      return name
      }
  3. 类泛型

    • 在定义类的时候, 我们也可以加入一些泛型限制一些内容

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      class Animal<T> {
      name: T

      constructor(name: T) {
      this.name = name
      }

      action<T>(say: T) {
      console.log(say)
      }
      }

      let cat = new Animal<string>('许小墨')

      cat.action<string>('Hello, world!')

断言

  • 断言是一个非常有意思的小玩意, 有的时候可以在我无助的时候给我带来一丝希望

  • 我一直觉得断言就是 “奥特曼”

    descript

  • 看一个例子

    1
    2
    3
    4
    5
    6
    7
    8
    // 获取了一个页面上的 div 元素
    const ele = document.querySelector('div')

    // 制作一个简单的功能函数, 要求参数接受一个 DIV 元素
    function util(ele: HTMLDivElement): void {}

    // 调用函数传入参数
    util(ele)
    • 在正常不过的一段代码了, 这个有啥的

    • 写完我们才发现

      descript

  • 为什么 ? 为什么 ?

    descript

  • 会不会是因为我定义 ele 变量的时候, 没有限制类型呢

    descript

  • 这回下面好了, 上面又出问题了

  • 解释一下吧

    • 其实是因为, 我们获取元素这个操作, 是有可能获取不到的
    • 当我们获取不到的时候就是 null
    • 就不是 HTMLDivElement 这个类型了
    • 所以在哪个地方限制好以后, 都会出现问题的
  • 怎么办呢, 这个时候, 就可以使用断言了

    • 断言其实就是我主观给他强制定义一个类型, 这样就不会出现问题了
    1
    2
    3
    4
    5
    const ele = document.querySelector('div') as HTMLDivElement

    function util(ele: HTMLDivElement): void {}

    util(ele)
  • 这里我就强制把 ele 元素断定为 HTMLDivElement 类型, 那么就不会出现问题了

  • 其实在很多时候, 我们不确定, 或者拿不准的时候, 我们都可以使用断言来解决问题

    • 先不让他报错, O(∩_∩)O~
    • 剩下的后面再说