软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。
泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。

12.1 泛型接口

  1. interface GenericIdentityFn<T> {
  2. (arg: T): T;
  3. }

函数泛性 generic

  1. function add<T>(a: T, b: T) {
  2. return `${a}${b}`
  3. }
  4. const res = add<string>('a', 'b')
  5. console.log(res) // ab
  6. function map<T>(params: T[]) {
  7. return params
  8. }
  9. const res = map<string>(['123'])
  10. console.log(res) // [ "123" ]
  11. // 多个泛型
  12. function add<T, P>(a: T, b: P) {
  13. return `${a}${b}`
  14. }
  15. const res = add<string, number>('a', 2)
  16. console.log(res) // a2
  17. // 指定返回类型
  18. function add<T>(a: T, b: T): T {
  19. return `${a}${b}`
  20. }
  21. const res = add<string>('a', 'b')
  22. console.log(res) // ab

12.2 泛型类

🌰 1

  1. class GenericNumber<T> {
  2. zeroValue: T;
  3. add: (x: T, y: T) => T;
  4. }
  5. let myGenericNumber = new GenericNumber<number>();
  6. myGenericNumber.zeroValue = 0;
  7. myGenericNumber.add = function (x, y) {
  8. return x + y;
  9. };

🌰 2

  1. class DataManager {
  2. constructor(private data: string[]) {}
  3. getItem(index: number): string {
  4. return this.data[index]
  5. }
  6. }
  7. const data = new DataManager(['6'])
  8. const res = data.getItem(0)
  9. console.log(res) // 6
  10. // 当传入值是多种类型时
  11. class DataManager<T> {
  12. constructor(private data: T[]) {}
  13. getItem(index: number): T {
  14. return this.data[index]
  15. }
  16. }
  17. const data = new DataManager<string>(['6'])
  18. const res = data.getItem(0)
  19. console.log(res)

更多🌰

  1. interface Item {
  2. name: string
  3. }
  4. class DataManager<T extends Item> {
  5. constructor(private data: T[]) {}
  6. getItem(index: number): string {
  7. return this.data[index].name
  8. }
  9. }
  10. const data = new DataManager([
  11. {
  12. name: 'Jak'
  13. }
  14. ])
  15. const res = data.getItem(0)
  16. console.log(res) // Jack

指定泛型类型

  1. class DataManager<T extends number | string> {
  2. constructor(private data: T[]) {}
  3. getItem(index: number): T {
  4. return this.data[index]
  5. }
  6. }
  7. const data = new DataManager<string>(['6'])
  8. const res = data.getItem(0)
  9. console.log(res) // 6

泛型作为一个具体的类型注解

  1. function hello<T>(params: T) {
  2. return params
  3. }
  4. const func: <T>(param: T) => T = hello

12.3 泛型变量

对刚接触 TypeScript 泛型的小伙伴来说,看到 T 和 E,还有 K 和 V 这些泛型变量时,估计会一脸懵逼。其实这些大写字母并没有什么本质的区别,只不过是一个约定好的规范而已。也就是说使用大写字母 A-Z 定义的类型变量都属于泛型,把 T 换成 A,也是一样的。下面我们介绍一下一些常见泛型变量代表的意思:

  • T(Type):表示一个 TypeScript 类型
  • K(Key):表示对象中的键类型
  • V(Value):表示对象中的值类型
  • E(Element):表示元素类型

    12.4 泛型工具类型

    为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType 等。出于篇幅考虑,这里我们只简单介绍 Partial 工具类型。不过在具体介绍之前,我们得先介绍一些相关的基础知识,方便读者自行学习其它的工具类型。
    1.typeof
    在 TypeScript 中,typeof 操作符可以用来获取一个变量声明或对象的类型。
    1. interface Person {
    2. name: string;
    3. age: number;
    4. }
    5. const sem: Person = { name: 'semlinker', age: 30 };
    6. type Sem= typeof sem; // -> Person
    7. function toArray(x: number): Array<number> {
    8. return [x];
    9. }
    10. type Func = typeof toArray; // -> (x: number) => number[]
    2.keyof
    keyof 操作符可以用来一个对象中的所有 key 值:
    1. interface Person {
    2. name: string;
    3. age: number;
    4. }
    5. type K1 = keyof Person; // "name" | "age"
    6. type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
    7. type K3 = keyof { [x: string]: Person }; // string | number
    3.in
    in 用来遍历枚举类型: ``` type Keys = “a” | “b” | “c” type Obj = {

} // -> { a: any, b: any, c: any }

  1. **4.infer**<br />在条件类型语句中,可以用 `infer` 声明一个类型变量并且对它进行使用。

type ReturnType = T extends ( …args: any[] ) => infer R ? R : any;

  1. 以上代码中 `infer R` 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。<br />**5.extends**<br />有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。

interface ILengthwise { length: number; } function loggingIdentity(arg: T): T { console.log(arg.length); return arg; }

  1. 现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:

loggingIdentity(3); // Error, number doesn’t have a .length property

  1. 这时我们需要传入符合约束类型的值,必须包含必须的属性:

loggingIdentity({length: 10, value: 3});

  1. **6.Partial**<br />`Partial<T>` 的作用就是将某个类型里的属性全部变为可选项 `?`。<br />**定义:**

/**

  • node_modules/typescript/lib/lib.es5.d.ts
  • Make all properties in T optional */ type Partial = { [P in keyof T]?: T[P]; };
    1. 在以上代码中,首先通过 `keyof T` 拿到 `T` 的所有属性名,然后使用 `in` 进行遍历,将值赋给 `P`,最后通过 `T[P]` 取得相应的属性值。中间的 `?` 号,用于将所有属性变为可选。<br />**示例:**
    interface Todo { title: string; description: string; } function updateTodo(todo: Todo, fieldsToUpdate: Partial) { return { …todo, …fieldsToUpdate }; } const todo1 = { title: “organize desk”, description: “clear clutter”, }; const todo2 = updateTodo(todo1, { description: “throw out trash”, });
    1. 在上面的 `updateTodo` 方法中,我们利用 `Partial<T>` 工具类型,定义 `fieldsToUpdate` 的类型为 `Partial<Todo>`,即:
    { title?: string | undefined; description?: string | undefined; } ```

泛型中keyof语法的使用

类型可以是字符串

  1. type NAME = 'name'
  2. const a: NAME = 'hello' // error
  3. const b: NAME = 'name'

index.ts

  1. interface Person {
  2. name: string;
  3. age: number;
  4. gender: string;
  5. }
  6. class Teacher {
  7. constructor(private info: Person) {}
  8. getInfo<T extends keyof Person>(key: T): Person[T] {
  9. // key: string 无法保护key是有效
  10. return this.info[key]
  11. }
  12. }
  13. const teacher = new Teacher({
  14. name: "Jack",
  15. age: 28,
  16. gender: "male",
  17. })
  18. const test = teacher.getInfo('name')
  19. console.log(test)