Skip to main content

泛型

基本使用

泛型常用于函数,使用泛型可以让函数的参数类型、返回值类型进行动态变化。

function identity<T>(arg: T): T {
return arg;
}

// equal
const identity = <T>(arg: T): T => arg;

// equal
const identiry = function<T>(arg: T): T {
return arg;
}

使用 <T> 相当于定义了一个泛型变量,它可能是任意类型,这由我们调用 identity 传入的泛型参数决定。

identity<number>(1); // 此时泛型变量 T 是一个 number 类型

并且泛型变量可以定义多个。

function identity<T, U>(arg: T, message: U): void {
console.log(arg, message);
}

identity<number, string>(111, "Hello World");

另外,泛型参数可以不用手动传入,在我们调用函数时,泛型参数可以由传入的函数参数类型进行自动类型推断。

function identity<T, U>(arg: T, message: U): void {
console.log(arg, message);
}

identity(111, "Hello World"); // T 是 number,U 是 string

泛型还可以用于对象类型。

interface Response<T> {
status: number;
data: {
data: T,
success: boolean
}
}

// equal
type Response<T> = {
status: number;
data: {
data: T,
success: boolean
}
};

泛型约束

默认情况下,一个泛型变量可能是任意类型,如果我们想获取函数参数上的某些属性,不对泛型进行约束是无法获取到的。

function foo<T>(arg: T): number {
return arg.length; // error,因为 T 可能是任何类型,并不能确定该类型上面具有 length 属性
}

我们可以使用 extends 后面接一个类型, 来约束泛型的类型。

function foo<T extends (string | number[])>(arg: T): number {
return arg.length; // 这里的 T 只可能是 string 或者 number[] 类型
}

或许你会问为什么不直接设置 arg 参数的类型为 string | number[],这样不是更方便吗?确实如此,在这个案例中并没有体现出泛型的优势,这个例子仅仅是帮助你理解泛型的约束。

你需要知道 extends 有继承作用,因此当泛型变量是一个对象类型时,泛型约束就很有作用了,来看看下面这个案例。

interface HasAge {
age: number;
}

// 返回 items 中 age 属性最大的那个 item
function getOldestAge(items: HasAge[]) {
return items.sort((a, b) => b.age - a.age)[0];
}

此时 getOldestAge 可以用于任何符合 HasAge 接口的对象。

const things = [{ age: 10 }, { age: 20 }, { age: 15 }];
const oldestThing = getOldest(things);

console.log(oldestThing.age); // 20 ✅

它的返回值是类型被推断为 HasAge,因此我们可以获取 oldestThingage 属性。

但是,如果出现更复杂的对象类型。

interface Person {
name: string;
age: number;
gender: 0 | 1;
}

const people: Person[] = [
{name: "kll", age: 18, gender: 1},
{name: "dwd", age: 20, gender: 0},
{name: "yukee", age: 12, gender: 1},
]

const oldestPerson = getOldestAge(people);

由于 TypeScript 的 鸭子辨型机制,因此我们可以正常地将 people 作为参数调用 getOldestAge 函数。但是有一个缺陷,getOldestAge 的返回值被推断为 HasAge,对于 oldestPerson 对象,我们无法获取它别的属性了。

console.log(oldestPerson.name, oldestPerson.gender); // error

下面,我们使用泛型来改造 getOldestAge 函数:

function getOldestAge<T extends HasAge>(items: T[]): T {
return items.sort((a, b) => b.age - a.age)[0];
}

const oldestPerson = getOldestAge(people); // ✅ type Person

这样一来 getOldestAge 就适用于一切具有 age 属性的对象了。

Summary
  • 泛型约束的作用是让泛型变量具有更明确的类型约束
  • 如果泛型约束的是基本类型,比如 <T extends (number | string)>,那么就会要求传入的参数必须是 number 或者 string 类型
  • 如果泛型约束的是一个对象类型,比如 <T extends {name: string}>,那么就会要求传入的参数必须具有 name 属性

Reference