泛型
基本使用
泛型常用于函数,使用泛型可以让函数的参数类型、返回值类型进行动态变化。
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
,因此我们可以获取 oldestThing
的 age
属性。
但是,如果出现更复杂的对象类型。
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
属性