TypeScript 泛型完全指南:写法、四大应用场景与高级用法
泛型是 TypeScript 中一种「类型化的变量」,它不预先指定具体的类型,而是在使用时才确定类型。简单来说,泛型就像一个「类型占位符」,可以在函数、接口、类、类型别名中被复用,从而实现一套代码适配多种类型 ,同时避免
any类型带来的类型信息丢失。
举个简单的例子:如果我们需要一个函数,能够返回数组的第一个元素,同时支持数字数组、字符串数组等多种类型,不用泛型的话,要么写多个重载函数,要么使用 any 类型:
typescript
// 弊端:丢失类型校验,返回值为 any 类型
function getFirst(arr: any[]): any {
return arr[0];
}
const str = getFirst(["a", "b", "c"]); // 类型为 any,无法获得自动补全
const num = getFirst([1, 2, 3]); // 类型为 any,存在类型安全风险
而使用泛型,我们可以完美解决这个问题:
typescript
// 泛型基础写法:用 <T> 定义类型参数,T 可看作任意合法的类型变量名
function getFirst<T>(arr: T[]): T {
return arr[0];
}
// 使用时自动推导类型,也可显式指定类型
const str = getFirst(["a", "b", "c"]); // 自动推导 T 为 string,返回值类型为 string
const num = getFirst<number>([1, 2, 3]); // 显式指定 T 为 number,返回值类型为 number
这里的 <T> 就是泛型的核心标识,T(Type 的缩写,也可以用 U、V、Data 等任意合法名称)就是我们定义的「类型参数」,它会在函数调用时被具体的类型替代,从而实现「一次定义,多类型复用」。
二、泛型的四大核心应用场景
泛型的应用场景主要集中在函数、接口、类、类型别名这四个方面,下面逐一拆解每种场景的写法与实战用法。
场景 1:泛型函数(最常用)
泛型函数是日常开发中使用频率最高的场景,核心写法是「在函数名后添加 <类型参数>」,类型参数可以在函数的参数、返回值、内部变量中使用。
基础写法
typescript
// 单个类型参数
function wrapValue<T>(value: T): { value: T } {
return { value };
}
// 多个类型参数(支持定义多个泛型变量,用逗号分隔)
function mergeObjects<U, V>(obj1: U, obj2: V): U & V {
return { ...obj1, ...obj2 };
}
// 实战使用
const wrapped = wrapValue<boolean>(true); // 返回 { value: boolean }
const merged = mergeObjects({ name: "张三" }, { age: 28 }); // 返回 { name: string; age: number }
关键特性:类型参数的默认值
和函数参数可以设置默认值一样,泛型的类型参数也可以设置默认值。当使用泛型时没有显式指定类型参数,且 TypeScript 无法自动推导时,会使用这个默认值。
语法:<T = 默认类型>
typescript
// 为类型参数 T 设置默认值 string
function getFirst<T = string>(arr: T[]): T {
return arr[0];
}
// 未显式指定类型,且无法推导具体类型时,使用默认值 string
const defaultVal = getFirst([]); // T 为 string,返回值类型为 string
// 自动推导类型,覆盖默认值
const numVal = getFirst([1, 2, 3]); // T 为 number,返回值类型为 number
场景 2:泛型接口
当接口需要支持多种类型的属性或方法时,泛型接口可以让接口具备更强的复用性。核心写法是「在接口名后添加 <类型参数>」,类型参数可用于接口的属性、方法参数和返回值。
基础写法
typescript
// 定义泛型接口
interface Result<T> {
code: number;
message: string;
data: T; // 用泛型 T 定义 data 属性的类型
}
// 使用时指定具体类型
type UserResult = Result<{ id: number; name: string }>;
type ArticleResult = Result<{ title: string; content: string }>;
// 实战应用:接口返回值类型定义
const userRes: UserResult = {
code: 200,
message: "请求成功",
data: { id: 1, name: "张三" }
};
const articleRes: ArticleResult = {
code: 200,
message: "请求成功",
data: { title: "TypeScript 泛型指南", content: "泛型的四大应用场景..." }
};
泛型接口与函数结合(常用实战场景)
typescript
// 定义泛型接口(描述函数类型)
interface RequestFunction<T> {
(url: string): Promise<Result<T>>;
}
// 实现该接口的函数
const getUserInfo: RequestFunction<{ id: number; name: string }> = async (url) => {
const res = await fetch(url);
return res.json();
};
带默认值的泛型接口
typescript
// 为泛型接口设置默认值
interface Pagination<T = unknown> {
page: number;
size: number;
total: number;
list: T[];
}
// 未指定类型,使用默认值 unknown
const defaultPagination: Pagination = {
page: 1,
size: 10,
total: 100,
list: []
};
// 指定具体类型,覆盖默认值
const userPagination: Pagination<{ id: number; name: string }> = {
page: 1,
size: 10,
total: 100,
list: [{ id: 1, name: "张三" }]
};
场景 3:泛型类
泛型类适用于「类的属性、方法需要支持多种类型」的场景,比如数据容器、队列、栈等。核心写法是「在类名后添加 <类型参数>」,类型参数可用于类的属性、构造函数、实例方法。
基础写法
typescript
// 定义泛型类
class Stack<T> {
private items: T[] = [];
// 入栈方法
push(item: T): void {
this.items.push(item);
}
// 出栈方法
pop(): T | undefined {
return this.items.pop();
}
// 获取栈顶元素
peek(): T | undefined {
return this.items[this.items.length - 1];
}
}
// 使用时指定具体类型
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2(类型为 number | undefined)
const stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
console.log(stringStack.peek()); // "b"(类型为 string | undefined)
带默认值的泛型类
typescript
// 为泛型类设置默认值
class Container<T = string> {
private data: T;
constructor(data: T) {
this.data = data;
}
getData(): T {
return this.data;
}
}
// 未指定类型,使用默认值 string
const defaultContainer = new Container("hello");
console.log(defaultContainer.getData()); // "hello"(类型为 string)
// 指定具体类型,覆盖默认值
const numberContainer = new Container<number>(123);
console.log(numberContainer.getData()); // 123(类型为 number)
注意:泛型类只作用于实例部分,静态属性和静态方法无法使用类的泛型参数(因为静态成员属于类本身,而泛型参数是在实例化时确定的)。
场景 4:泛型类型别名
类型别名(type)是 TypeScript 中定义自定义类型的重要方式,泛型类型别名可以让自定义类型具备更强的灵活性,核心写法是「在类型别名后添加 <类型参数>」。
基础写法
typescript
// 定义泛型类型别名
type Pair<T> = [T, T]; // 元组类型,两个元素类型相同
type Optional<T> = T | undefined; // 可选类型,T 或 undefined
type RecordMap<K extends string | number, V> = { [key in K]: V }; // 映射类型
// 使用时指定具体类型
type NumberPair = Pair<number>; // [number, number]
type OptionalString = Optional<string>; // string | undefined
type UserMap = RecordMap<number, { name: string; age: number }>; // { [key: number]: { name: string; age: number } }
// 实战应用
const pair: NumberPair = [1, 2];
const optionalStr: OptionalString = "hello";
const userMap: UserMap = {
1: { name: "张三", age: 28 },
2: { name: "李四", age: 30 }
};
带默认值的泛型类型别名
typescript
// 为泛型类型别名设置默认值
type ResponseData<T = { [key: string]: unknown }> = {
success: boolean;
data: T;
};
// 未指定类型,使用默认值
const defaultResponse: ResponseData = {
success: true,
data: { id: 1, name: "张三" }
};
// 指定具体类型,覆盖默认值
const userResponse: ResponseData<{ id: number; name: string }> = {
success: true,
data: { id: 1, name: "张三" }
};
三、高级用法:类型参数的约束条件
在某些场景下,我们不希望泛型参数是「任意类型」,而是希望它满足一定的约束条件(比如拥有某个属性、实现某个接口)。这时可以使用 extends 关键字来约束泛型参数,这就是「泛型约束」。
基础约束:extends 关键字
typescript
// 定义一个接口,作为约束条件
interface HasLength {
length: number;
}
// 约束 T 必须实现 HasLength 接口(即 T 必须拥有 length 属性)
function getLength<T extends HasLength>(value: T): number {
return value.length;
}
// 合法:string 拥有 length 属性
console.log(getLength("hello")); // 5
// 合法:数组拥有 length 属性
console.log(getLength([1, 2, 3])); // 3
// 合法:自定义对象拥有 length 属性
console.log(getLength({ length: 10, name: "测试" })); // 10
// 不合法:number 没有 length 属性,编译报错
console.log(getLength(123)); // Error: Argument of type 'number' is not assignable to parameter of type 'HasLength'
泛型约束与默认值结合
泛型约束和默认值可以同时使用,语法顺序为「<T extends 约束类型 = 默认类型>」:
typescript
interface HasId {
id: number;
}
// 约束 T 必须实现 HasId 接口,同时设置默认值
function getId<T extends HasId = { id: number; name: string }>(obj: T): number {
return obj.id;
}
// 未指定类型,使用默认值(满足 HasId 约束)
const defaultId = getId({ id: 1, name: "张三" }); // 1
// 指定具体类型(满足 HasId 约束)
const customId = getId({ id: 2, age: 28 }); // 2
常用约束:约束为对象、字符串/数字字面量
typescript
// 约束 T 为对象类型
function getProperty<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "张三", age: 28 };
console.log(getProperty(user, "name")); // "张三"(类型为 string)
console.log(getProperty(user, "age")); // 28(类型为 number)
// 约束 K 为 "success" | "error" 字面量类型
function createMessage<K extends "success" | "error" = "success">(type: K): { type: K; content: string } {
return {
type,
content: type === "success" ? "操作成功" : "操作失败"
};
}
const successMsg = createMessage(); // { type: "success"; content: string }
const errorMsg = createMessage("error"); // { type: "error"; content: string }
四、泛型使用总结与最佳实践
- 泛型的核心价值 :复用代码、保留类型信息、避免
any类型,实现「灵活与安全并存」。 - 四大应用场景优先级:泛型函数 > 泛型接口 > 泛型类型别名 > 泛型类(类的泛型使用场景相对有限)。
- 最佳实践 :
- 类型参数命名尽量语义化(如
T表示通用类型,U/V表示辅助类型,K表示键类型,V表示值类型)。 - 对于有常用类型的场景,设置泛型默认值,提升代码易用性。
- 当需要限制泛型范围时,使用
extends进行约束,避免非法类型传入。 - 避免过度泛型化:如果一个函数只需要支持一种或两种固定类型,无需使用泛型,直接定义具体类型即可。
- 类型参数命名尽量语义化(如