泛型是 TypeScript 最强大的特性之一。它让你写出可复用且类型安全的代码。
5.1 为什么需要泛型?
假设要写一个"返回数组第一个元素"的函数:
// 方案 1:写死类型 → 不通用
function firstNumber(arr: number[]): number {
return arr[0];
}
// 方案 2:用 any → 丢失类型信息
function firstAny(arr: any[]): any {
return arr[0];
}
const result = firstAny([1, 2, 3]); // result 是 any,不知道是 number
// 方案 3:用泛型 → 既通用又安全 ✅
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const a = first([1, 2, 3]); // a: number
const b = first(["x", "y"]); // b: string
const c = first([true, false]); // c: boolean
<T>就是类型参数,调用时 TypeScript 会自动推断T是什么类型。
5.2 泛型函数
// 单个类型参数
function identity<T>(value: T): T {
return value;
}
identity(42); // T 推断为 number
identity("hello"); // T 推断为 string
// 多个类型参数
function pair<A, B>(first: A, second: B): [A, B] {
return [first, second];
}
const p = pair("name", 25); // [string, number]
// 显式指定类型参数
const n = identity<number>(42);
5.3 泛型接口
// 泛型接口
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// 使用时指定 T
const userRes: ApiResponse<{ name: string; age: number }> = {
code: 200,
message: "success",
data: { name: "张三", age: 25 },
};
const listRes: ApiResponse<string[]> = {
code: 200,
message: "success",
data: ["a", "b", "c"],
};
泛型函数接口
interface Transformer<Input, Output> {
(value: Input): Output;
}
const stringify: Transformer<number, string> = (n) => n.toString();
const parse: Transformer<string, number> = (s) => Number(s);
5.4 泛型类
class Container<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
setValue(newValue: T): void {
this.value = newValue;
}
}
const numContainer = new Container<number>(42);
numContainer.getValue(); // number
numContainer.setValue(100);
const strContainer = new Container("hello");
strContainer.getValue(); // string
实用示例:简单的 EventEmitter
class EventEmitter<Events extends Record<string, any>> {
private handlers: Partial<Record<keyof Events, Function[]>> = {};
on<K extends keyof Events>(event: K, handler: (data: Events[K]) => void) {
if (!this.handlers[event]) {
this.handlers[event] = [];
}
this.handlers[event]!.push(handler);
}
emit<K extends keyof Events>(event: K, data: Events[K]) {
this.handlers[event]?.forEach((fn) => fn(data));
}
}
// 定义事件类型
interface AppEvents {
login: { userId: string };
logout: undefined;
message: { text: string; from: string };
}
const emitter = new EventEmitter<AppEvents>();
emitter.on("login", (data) => {
console.log(data.userId); // ✅ TS 知道 data 的类型
});
emitter.on("message", (data) => {
console.log(data.text); // ✅
});
5.5 泛型约束(extends)
限制类型参数必须满足某些条件:
// T 必须有 length 属性
function logLength<T extends { length: number }>(value: T): void {
console.log(value.length);
}
logLength("hello"); // ✅ string 有 length
logLength([1, 2, 3]); // ✅ 数组有 length
logLength({ length: 5 }); // ✅
logLength(42); // ❌ number 没有 length
keyof 约束
// K 必须是 T 的属性名
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "张三", age: 25, email: "a@b.com" };
getProperty(user, "name"); // ✅ 返回 string
getProperty(user, "age"); // ✅ 返回 number
getProperty(user, "phone"); // ❌ "phone" 不在 User 的键中
多重约束
interface HasId {
id: number;
}
interface HasName {
name: string;
}
// T 必须同时满足 HasId 和 HasName
function display<T extends HasId & HasName>(item: T): string {
return `#${item.id}: ${item.name}`;
}
5.6 泛型默认类型
// 默认 T 为 string
interface Container<T = string> {
value: T;
}
const a: Container = { value: "hello" }; // T = string
const b: Container<number> = { value: 42 }; // T = number
// 函数也可以有默认类型
function createArray<T = number>(length: number, fill: T): T[] {
return new Array(length).fill(fill);
}
5.7 内置工具类型
TypeScript 提供了很多基于泛型的内置工具类型,非常实用:
Partial<T> —— 所有属性变可选
interface User {
name: string;
age: number;
email: string;
}
type PartialUser = Partial<User>;
// 等同于:
// {
// name?: string;
// age?: number;
// email?: string;
// }
// 常用于更新操作
function updateUser(id: number, updates: Partial<User>): void {
// 只需要传要更新的字段
}
updateUser(1, { name: "新名字" }); // ✅ 不用传所有字段
Required<T> —— 所有属性变必选
interface Config {
host?: string;
port?: number;
}
type RequiredConfig = Required<Config>;
// { host: string; port: number; }
Readonly<T> —— 所有属性变只读
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { name: "张三", age: 25, email: "a@b.com" };
user.name = "李四"; // ❌ 只读
Pick<T, K> —— 挑选部分属性
type UserBasic = Pick<User, "name" | "age">;
// { name: string; age: number; }
Omit<T, K> —— 排除部分属性
type UserWithoutEmail = Omit<User, "email">;
// { name: string; age: number; }
Record<K, V> —— 创建键值映射
type UserRole = "admin" | "user" | "guest";
type Permissions = Record<UserRole, string[]>;
// {
// admin: string[];
// user: string[];
// guest: string[];
// }
Extract<T, U> 和 Exclude<T, U>
type A = "a" | "b" | "c" | "d";
type OnlyAB = Extract<A, "a" | "b">; // "a" | "b"
type NotAB = Exclude<A, "a" | "b">; // "c" | "d"
NonNullable<T>
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>; // string
ReturnType<T> 和 Parameters<T>
function fetchData(url: string, timeout: number): Promise<any> {
return fetch(url);
}
type FetchReturn = ReturnType<typeof fetchData>; // Promise<any>
type FetchParams = Parameters<typeof fetchData>; // [string, number]
5.8 泛型实战模式
工厂函数
function createInstance<T>(constructor: new () => T): T {
return new constructor();
}
类型安全的 API 请求
async function request<T>(url: string, options?: RequestInit): Promise<T> {
const res = await fetch(url, options);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json() as Promise<T>;
}
// 使用时指定返回类型
interface User {
id: number;
name: string;
}
const user = await request<User>("/api/user/1");
console.log(user.name); // ✅ TS 知道有 name 属性
管道/链式调用
function pipe<A, B>(fn1: (a: A) => B): (a: A) => B;
function pipe<A, B, C>(fn1: (a: A) => B, fn2: (b: B) => C): (a: A) => C;
function pipe<A, B, C, D>(
fn1: (a: A) => B,
fn2: (b: B) => C,
fn3: (c: C) => D,
): (a: A) => D;
function pipe(...fns: Function[]) {
return (input: any) => fns.reduce((acc, fn) => fn(acc), input);
}
const transform = pipe(
(s: string) => s.length, // string → number
(n: number) => n > 5, // number → boolean
);
transform("hello world"); // true
📝 练习
- 实现一个泛型函数
last<T>(arr: T[]): T | undefined - 实现
merge<A, B>(a: A, b: B): A & B - 用泛型约束实现
pluck<T, K extends keyof T>(arr: T[], key: K): T[K][] - 用内置工具类型:给
User创建CreateUserDTO(不包含 id)和UpdateUserDTO(所有字段可选)
// 参考答案
// 1
function last<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
// 2
function merge<A extends object, B extends object>(a: A, b: B): A & B {
return { ...a, ...b };
}
// 3
function pluck<T, K extends keyof T>(arr: T[], key: K): T[K][] {
return arr.map((item) => item[key]);
}
const users = [
{ name: "张三", age: 25 },
{ name: "李四", age: 30 },
];
pluck(users, "name"); // ["张三", "李四"],类型 string[]
// 4
interface User {
id: number;
name: string;
age: number;
email: string;
}
type CreateUserDTO = Omit<User, "id">;
type UpdateUserDTO = Partial<Omit<User, "id">>;