• TypeScript——泛型理论与实践


    1. 简介

    软件工程的一个重要部分就是构建组件,组件不仅需要有定义良好和一致的
    API,还需要是可复用的。好的组件不仅能够兼容现有的数据类型,也能适用于未来可能出现的数据类型,这在构建大型软件系统时会有很大的灵活度以及很高的扩展性。
    在比如 C# 和 Java
    语言中,用来创建可复用组件的工具,我们称之为泛型。利用泛型,我们可以创建一个支持众多类型的组件,并且用户在使用组件时可以传入自己想要的类型。

    2. 举个🌰

    上述的简介还是不够通俗,我们接下来结合一些简单代码来理解什么是泛型:

    function getVal(val: string): string {
        return val
    }
    
    const foo = getVal('123')
    
    const bar = getVal(123)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    我们定义了一个函数 getVal,指定接收一个类型为 string 的参数,并将入参作为返回值。
    结合代码图示,我们老老实实传入 string 类型的值后,可以看到代码正常,并且 TypeScript 很智能的给出编码提示。在我们传入 number 类型的值时也会很及时的给出报错信息,这很好、很强大!
    但是,如果我们编写的方法就是需要接收多种类型的参数,那该如何呢?🤔
    小明同学不假思索的写出以下代码:

    function getVal(val: string | number): string | number {
        return val
    }
    
    const foo = getVal(123)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    通过联合类型(Union Types)实现了 getVal 函数接收多种类型参数的需求,并且运行完美没有任何报错。只是由于返回值也是一个联合类型,所以 TypeScript 并不能准确知道函数具体的返回值类型。因此导致 TypeScript 给出的代码提示是两种类型值的共有方法:

    在这里插入图片描述

    我们想要实现的函数功能是:接收任意类型的入参,返回值等同于入参的类型,这很明显不符合我们的预期。

    3. 泛型——函数

    我们可以使用泛型(Generics)来实现上面的函数功能:

    function getVal<T>(val: T): T {
        return val
    }
    
    const foo = getVal(123)
    
    const bar = getVal('123')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面示例中,函数 getVal() 的函数名后面尖括号的部分 ,就是泛型的类型参数,参数要放在一对尖括号 <> 里面。
    泛型的类型参数本质就是一个接收 TypeScript类型,它和 js 中函数定义的参数(形参)极为类似,只不过在 js 中形参接收的是值,在 TypeScript 中类型参数接收的是类型。

    在这里插入图片描述

    在这里插入图片描述

    通过使用类型参数接收指定类型,我们精准的得到函数返回值类型,并享受更好的智能提示。
    💡大多数时候我们不必显式的传入类型参数,因为 TypeScript 可以根据传入的值推断出它的类型,所以更推荐下面的写法:

    function getVal<T>(val: T): T {
        return val
    }
    
    const foo = getVal(123)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    4. 泛型——接口

    通过上面的内容,我们已经感受到泛型很方便、灵活,然而它的强大之处才体现出冰山一角。

    1. 使用 interface 描述一个对象时,动态定义某个属性的类型
    interface Data<T> {
      name: string;
      val: T;
    }
    
    const foo: Data<number> = {
      name: "tom",
      val: 123,
    };
    
    const bar: Data<string> = {
      name: "tom",
      val: "123",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. 使用 interface 定义一个泛型函数的类型
    function id<Type>(arg: Type): Type {
      return arg;
    }
    
    let myId
    
    myId = id
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    普通函数的类型好定义 type Fn = (arg: any) => void ,泛型函数的类型如何定义呢?
    其实编辑器也是可以给出答案的:

    在这里插入图片描述

    粘一下代码即可:

    interface Fn {
        <Type>(arg: Type): Type
    }
    
    function id<Type>(arg: Type): Type {
      return arg;
    }
    
    let myId: Fn
    
    myId = id // ✔️
    
    myId = 123 // ❌
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    5. 泛型——类

    泛型类的类型参数写在类名后面:

    class Person<Name, Age> {
      name: Name;
      age: Age;
    
      constructor(name: Name, age: Age) {
        this.name = name;
        this.age = age;
      }
    }
    
    new Person<string, number>("Bob", 88);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    下面是继承泛型类的例子:

    class Person<Name, Age> {
      name: Name;
      age: Age;
    
      constructor(name: Name, age: Age) {
        this.name = name;
        this.age = age;
      }
    }
    
    class Teacher extends Person<string, number> {
        gender: string = 'male'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面示例中, Person 类有两个类型参数,分别是 NameAge,使用时必须给出 NameAge 的类型,所以 Teacher 类继承时按照需要把类型参数写进去就行了 Person
    但是如果我们想在初始化 Teacher 类的时候动态传入 Person 类的类型参数,那该如何呢?🤔🤔
    写法应该是一样的,我们可以尝试写出以下代码:

    class Person<Name, Age> {
      name: Name;
      age: Age;
    
      constructor(name: Name, age: Age) {
        this.name = name;
        this.age = age;
      }
    }
    
    class Teacher<Name, Age, Gender> extends Person<Name, Age> {
      gender: Gender;
    
      constructor(name: Name, age: Age, gender: Gender) {
        super(name, age);
        this.gender = gender;
      }
    }
    
    type Gender = "male" | "woman";
    
    const teacher = new Teacher<string, number, Gender>("Bob", 88, "male");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    我们可以看到,类型正常运作,结果也正常:

    在这里插入图片描述

    6. 泛型——类型别名

    相较于接口、类中的泛型运用,在类型别名中泛型的使用可能更常见一点:

    type CustomUnion<T> = T | string
    
    • 1

    以上是泛型的简单应用,通过动态的传入类型,便拥有了一个自定义的联合类型。
    下面是定义树形结构的例子:

    type Tree<T> = {
      value: T;
      children: Tree<T>[];
    };
    
    const tree: Tree<string> = {
      value: "tree-1",
      children: [
        {
          value: "tree-1-1",
          children: [],
        },
      ],
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在类型别名内部递归引用自身时,传入泛型,依然能够很好的工作:

    在这里插入图片描述

    7. 泛型——类型参数默认值

    我们在上面讲到 “类型参数极其类似 js 中函数的形参”,在 ES6 中函数的形参可以定义默认值,这很优雅,非常方便。在泛型中定义默认类型同样非常类似:

    // 定义类型参数默认类型
    function getVal<T = string>(val: T): T {
        return val
    }
    
    • 1
    • 2
    • 3
    • 4

    上面示例中,T = string 表示类型参数的默认值是 string。调用 getVal() 时,如果不给出 T 的值,TypeScript 就认为 T 等于 string
    但是,因为 TypeScript 会从实际参数推断出 T 的值,从而覆盖掉默认值,所以下面的代码不会报错。

    getVal(true) //✔️✔️
    
    • 1

    在这里插入图片描述

    8. 泛型——类型约束

    很多时候类型参数并不是无限制的,我们也可以对于传入的类型制定约束条件。

    type ButtonType = 'default' | 'primary'
    
    function getButtonType<T>(type: T): T {
        return type
    }
    
    getButtonType('123')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    就像这个🌰,我们传入 '123' 后运行很稳定、不报错,但是很明显 getButtonType 的参数不能随便传。
    根据代码块内容,它只能接收 'default''primary' 这两个参数,我们如何做到呢?🤔🤔🤔
    这种情况下真乃是泛型约束的绝佳应用场景!泛型约束的语法尤为简单,像 T extends string 这样。
    在 TypeScript中,T extends string 是一个类型限界表达式,它表示类型参数 T 必须是一个字符串类型或字符串类型的子类型。我们来轻微改写一下:

    type ButtonType = 'default' | 'primary'
    
    function getButtonType<T extends ButtonType>(type: T): T {
        return type
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样改过之后,不仅可以准确的给出报错提示,

    在这里插入图片描述

    还能给我们提供便捷的智能提示,这对于团队协作的可靠性及高效率也有着很大的提升!

    在这里插入图片描述

  • 相关阅读:
    【ai】tx2-nx 开通samba
    比 O(nlog(n)) 做得更好——2.改变问题以及排序和填充数组
    设了止损就万事大吉了?白银漫谈(一)
    数据分析思维分析方法和业务知识——业务指标
    神经网络控制simulink仿真,神经网络控制系统仿真
    NBU计算机大三下期末考
    如何实现主机与容器之间数据的同步?以nginx:v1镜像为例,进行验证。提交操作步骤
    leetcode.2401. 最长优雅子数组
    Qt 设置CPU亲缘性,把进程和线程绑定到CPU核心上(Linux)
    深入理解vue2.x双向数据绑定原理
  • 原文地址:https://blog.csdn.net/dizuncainiao/article/details/132878136