1. 为什么要使用泛型
使用泛型来创建可重用的组件,使得一个组件可以支持多种类型的数据,这样用户就可以以自己的数据类型来使用组件。
//泛型的基础语法
function func <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(func<Number, string>(68, "小潘"));
//也可以省略尖括号里面的声明类型,编译器能自动识别
2. 在函数中使用泛型
-
分配泛型参数
function identity<T>(value: T): T { return value; } const result = identity(123);
-
直接传递类型参数
type ProgrammingLanguage = { name: string; }; function identity<T>(value: T): T { return value; } const result = identity<ProgrammingLanguage>({ name: "TypeScript" });
type User = {
name: string;
}
async function fetchApi<ResultType>(path: string): Promise<ResultType> {
const response = await fetch(`https://example.com/api${path}`);
return response.json();
}
const data = await fetchApi<User[]>('/users')
//注:此例中,泛型ResultType并没有在参数列表中使用,也没有在TypeScript能够推断其值的其他地方使用,这意味着在调用函数时,必须显式地传递此泛型的类型
-
默认类型参数
在上述的例子中,调用代码时必须提供类型参数。如果调用代码不包含泛型类型参数,则 ResultType 将推断为 unknow,这时可以为泛型类型参数添加默认类型。通过在泛型类型参数后面添加 = DefaultType 来完成
async function fetchApi<ResultType = Record<string, any>>(path: string): Promise<ResultType> {
const response = await fetch(`https://example.com/api${path}`);
return response.json();
}
const data = await fetchApi('/users')
console.log(data.a)
//注:此处的Record<string, any>仅仅表示具有string类型的键和any类型的值的对象
-
参数类型约束
//此函数用于接受任何对象并返回另一个对象,其key 值与原始对象相同,但所有值都转换为字符串(映射类型) function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) { return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: JSON.stringify(obj[key]) }), {} as { [K in keyof T]: string }) }
常用场景一:确保属性存在
function identity<T>(arg: T): T {
console.log(arg.length); // Error
return arg;
}
//编译器将不会知道 T 确实含有 length 属性,尤其是在可以将任何类型赋给类型变量 T 的情况下
//应该修改如下
interface Length {
length: number;
}
function identity<T extends Length>(arg: T): T {
console.log(arg.length); // 可以获取length属性
return arg;
}
常用场景二: 检查对象上的某个键是否存在
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
通过 keyof 操作符,我们就可以获取指定类型的所有键,之后我们就可以结合 extends 约束,即限制输入的属性名包含在 keyof 返回的联合类型中
3. 在接口、类和类型中使用泛型
-
接口和类中的泛型
interface MyInterface<T> { field: T } class MyClass<T> { field: T constructor(field: T) { this.field = field } }
-
自定义类型中的泛型
type MyIdentityType<T> = T type B = MyIdentityType<number> //在这种情况下,B的类型就是number
4. 使用泛型创建映射类型
使用 TypeScript 时,有时需要创建一个与另一种类型具有相同结构的类型,即有相同的属性,但属性的类型不同。这时可以使用映射类型可以重用初始类型并减少重复代码。这种结构称为映射类型并依赖于泛型
type BooleanFields<T> = {
[K in keyof T]: boolean;
};
type User = {
email: string;
name: string;
}
type UserFetchOptions = BooleanFields<User>;