likes
comments
collection
share

详解 TS 中的泛型

作者站长头像
站长
· 阅读数 64

泛型简介

泛型程序设计是一种编程风格或编程范式,它允许在程序中定义形式类型参数,然后在泛型实例化时使用实际类型参数来替换形式类型参数,这一过程有些类似于函数形参在被函数调用时传入的实参替换

通过泛型可以定义通用的数据结构,增加 TypeScript 代码中类型的通用性,泛型的结构如下:

<T, U, V> // <> 括号内的就是泛型,这里T, U, V 都是形参
或者
<string// 这里是实参 string 类型

形参的名称为合法的变量标识符,通常约定首字母大写表示它是类型,一般名称使用简写 T,U,V,W 依次递增。

泛型基本应用

假设要实现一个函数其返回由传入参数组成数组,且传入参数类型保持一致,其 JS 版本代码如下:

function toArray(ab) {
 return [a, b]
}

使用 TypeScript 可以写成这样:

function toArray(a: number, b: number): number[] {
 return [a, b]
}

详解 TS 中的泛型

这样写的话 toArray 函数只能传入 number 类型,不能传入 string object 等其他类型,要想能够传入通用类型,可以使用 anyunknown 类型改为如下:

function toArray(a: unknown, b: unknown): unknown[] {
 return [a, b]
}

但是,这样写就丢失了形式参数类型 a b 要保持类型一致,返回的参数类型与形式参数类型的关联关系的约束信息。

这里传入 string 类型,返回的任意类型组成的数组

详解 TS 中的泛型

这里两个形参分别传入 number string 类型,没有检查出来两个形参类型不同,并且返回值是任意类型组成的数组

详解 TS 中的泛型

借助泛型的话,可以这么写:

function toArray<T>(a: T, b: T): T[] {
 return [a, b]
}

返回值的类型明确了是 stirng[] 类型

详解 TS 中的泛型

这里也可以不传递 <string> 泛型,借助 TypeScript 内置的类型推导可以根据 toArray 实际传入的参数类型推导出泛型参数 T 的实际类型

当传入参数类型不一致时也有类型校验

详解 TS 中的泛型

泛型约束

泛型可以通过 extends 关键字来定义泛型约束,下面代码表示泛型 T 必须是 string 类型的子类型,即 T 可以赋值给 string 类型。

<T extends string>

给上述 toArray 方法添加泛型约束如下:

interface Point {
 x: number;
 y: number;
}
function toArray<T extends Point>(a: T, b: T): T[] {
 return [a, b]
}

当传入形式参数的类型为 number 类型时不满足泛型约束会报错

详解 TS 中的泛型

基约束

每个类型参数都有基约束,与其是否在形式类型参数上定义泛型约束无关,类型参数的实际类型一定是其基约束的子类型,基约束的规则参照如下三个规则

三个规则

  • 规则一 如果类型参数声明了泛型约束,泛型约束为另一个类型参数 U,则类型参数 T 的基约束为类型参数 U
<T extends U>
  • 规则二 如果类型参数声明了泛型约束,泛型约束为某一个具体类型 Type,则类型参数 T 的基约束为类型 Type
<T extends boolean>

这里泛型 T 的基约束为类型 boolean

  • 规则三 如果类型参数没有声明泛型约束,则类型参数 T 的基约束为[[空对象类型字面量]]"{}"
<T>

泛型函数

[[函数签名]]中带有类型参数,该函数就是泛型函数,上面定义的 toArray 函数就是一个泛型函数。

详解 TS 中的泛型

// 普通函数签名
<T>(x: T): T
// 构造函数签名
new <T>(x: T): T[]

泛型接口

接口的定义中带有类型参数,该接口就是泛型接口

interface MyArray<T> extends Array<T> {
 first: T | undefined;
 last: T | undefined;
}

/lib.es5.d.ts 中对 Array 泛型接口的定义节选部分如下:

interface Array<T> {
    /**
     * Gets or sets the length of the array. This is a number one higher than the highest index in the array.
     */
    length: number;
    /**
     * Returns a string representation of an array.
     */
    toString(): string;
    /**
     * Returns a string representation of an array. The elements are converted to string using their toLocaleString methods.
     */
    toLocaleString(): string;
    /**
     * Removes the last element from an array and returns it.
     * If the array is empty, undefined is returned and the array is not modified.
     */
    pop(): T | undefined;
    /**
     * Appends new elements to the end of an array, and returns the new length of the array.
     * @param items New elements to add to the array.
     */
    push(...items: T[]): number;
    /**
     * Combines two or more arrays.
     * This method returns a new array without modifying any existing arrays.
     * @param items Additional arrays and/or items to add to the end of the array.
     */
    concat(...items: ConcatArray<T>[]): T[];
    /**
     * Combines two or more arrays.
     * This method returns a new array without modifying any existing arrays.
     * @param items Additional arrays and/or items to add to the end of the array.
     */
    concat(...items: (T | ConcatArray<T>)[]): T[];
    /**
     * Adds all the elements of an array into a string, separated by the specified separator string.
     * @param separator A string used to separate one element of the array from the next in the resulting stringIf omitted, the array elements are separated with a comma.
     */
    join(separator?: string): string;
}

泛型类

类的定义中带有类型参数,该类就是泛型类

// 类声明
class Container<T> {
 constructor(private readonly data: T) {}
}
const a = new Container<boolean>(true);

// 类表达式
const Container = class<T> {
 constructor(private readonly data: T) {}
}
const a = new Container<boolean>(true);

类的泛型类型参数不能用于类的静态成员,泛型类描述的是类的实例类型(需要实例化类时类型参数才有实际的类型),而类的静态成员是类构造函数类型的一部分,这个可以参阅下方的类类型

详解 TS 中的泛型

类类型

类声明、类表达式相当于声明了类的实例类型构造函数类型,类本身的类型是构造函数类型,当类作为类类型时就是实例类型

实例类型

TypeScript 中类声明、类表达式会引入一个新的命名类型——与类同名的类类型,这正是类可以作为类型的原因。

下面 Container 可以用作类型,并且它是兼容 ContainerType 类型的,ContainerContainerType 类型可以看成是等价的。

详解 TS 中的泛型

可以理解为:TypeScript 内部根据类的实例属性生成了相应的接口来表示类的实例类型。

构造函数类型

在定义类时,实际上是定义了一个构造函数,ES6 中类的本质也是如此。

这里 AConstructor 接口与 Container 类型等价,并且静态属性 version 定义在构建函数类型上。

详解 TS 中的泛型

内置泛型

TypeScript 中内置了大量的工具类型,这些工具类型都是借助泛型实现根据已有类型来创造新类型的,这是对泛型最好的应用,下面看看几个最常用内置泛型工具。

Partial

Partial<T>

创造一个新的类型,并将实际类型参数 T 中所有属性变为可选属性。

详解 TS 中的泛型

Required

Required<T>

创造一个新的类型,并将实际类型参数 T 中所有属性变为必选属性。

详解 TS 中的泛型

Record

Record<T,U>

创建一个新的对象类型,实际类型参数 T 为联合类型将作为新对象类型的属性,类型参数 T 为对象类型属性的类型。

详解 TS 中的泛型

Pick

Pick<T,U>

挑选出对象类型 T 中 U 对应的属性和类型,以此来创建一个新的对象类型。

详解 TS 中的泛型

当类型 T 中不存在要挑选的属性会报错

详解 TS 中的泛型

Omit

Omit<T,U>

Pick的功能是互补的,挑选出对象类型 T 中不在 U 中的属性和类型,以此来创建一个新的对象类型。

详解 TS 中的泛型

当类型 T 中不存在要挑选的属性不会报错

详解 TS 中的泛型

Exclude

Exclude<T,U>

从类型 T 中剔除所有可以赋值给类型 U 的类型。

详解 TS 中的泛型

当把类型 T 的所有类型都剔除光了,会得到 never 类型。下面代码中字面量类型 a b c 都是 string 的子类型(都可以赋值给 string 类型),于是 Cnever 类型。

详解 TS 中的泛型

Extract

Extract<T,U>

Exclude的功能是互补的,从类型 T 中获取所有可以赋值给类型 U 的类型。

详解 TS 中的泛型

详解 TS 中的泛型

泛型常见错误

interface Point {
 xnumber;
 ynumber;
}

function identity<T extends Point>(x: T): T {
 return {
  x0,
  y0,
 }
}

这里泛型参数 T 的约束为 Point 类型,函数的泛型定义表达的意思是:

  1. 参数 x 和返回值类型相同
  2. 参数 x 和返回值都满足类型约束

根据[[鸭式辨型]],我们知道返回值 { x: 0, y: 0 } 满足类型约束 T extends Point ,但是它与 Point 类型并不相同,故不满足上述第 2 点

详解 TS 中的泛型

这里已经知道返回值类型满足类型约束,可以使用[[类型断言]]将返回值断言成 T 类型解决这个类型报错

详解 TS 中的泛型

转载自:https://juejin.cn/post/7133810035171262501
评论
请登录