跳到主要内容

TypeScript 泛型

泛型(Generics)是创建可复用组件的核心特性,允许组件支持多种类型而不是单一类型。

泛型基础

为什么需要泛型

// 不使用泛型 - 类型丢失
function identity(arg: any): any {
return arg;
}

const result = identity("hello"); // result 类型是 any

// 使用泛型 - 保留类型
function identityGeneric<T>(arg: T): T {
return arg;
}

const resultStr = identityGeneric("hello"); // resultStr 类型是 string
const resultNum = identityGeneric(123); // resultNum 类型是 number

解释:泛型 T 是一个类型变量,在调用时由传入的参数类型决定。这样可以保留输入和输出的类型关系。

泛型函数

// 基本语法
function identity<T>(arg: T): T {
return arg;
}

// 调用方式一:类型推断
let output1 = identity("hello"); // string

// 调用方式二:显式指定类型
let output2 = identity<string>("hello"); // string

// 箭头函数写法
const identityArrow = <T>(arg: T): T => arg;

// 在 JSX 中需要使用 extends 避免与 JSX 标签混淆
const identityJSX = <T extends unknown>(arg: T): T => arg;

泛型接口

定义泛型接口

// 泛型接口
interface Container<T> {
value: T;
getValue(): T;
setValue(value: T): void;
}

// 实现
const numberContainer: Container<number> = {
value: 123,
getValue() { return this.value; },
setValue(value) { this.value = value; }
};

const stringContainer: Container<string> = {
value: "hello",
getValue() { return this.value; },
setValue(value) { this.value = value; }
};

函数类型泛型接口

interface GenericFunction<T> {
(arg: T): T;
}

// 使用
const myFunction: GenericFunction<number> = (arg) => arg * 2;
myFunction(5); // 10

泛型类

基本泛型类

class Box<T> {
private content: T;

constructor(content: T) {
this.content = content;
}

getContent(): T {
return this.content;
}

setContent(content: T): void {
this.content = content;
}
}

// 使用
const numberBox = new Box<number>(123);
console.log(numberBox.getContent()); // 123

const stringBox = new Box<string>("hello");
console.log(stringBox.getContent()); // "hello"

泛型约束类

interface Comparable {
compareTo(other: this): number;
}

class SortedList<T extends Comparable> {
private items: T[] = [];

add(item: T): void {
// 按顺序插入
let i = 0;
while (i < this.items.length && this.items[i].compareTo(item) < 0) {
i++;
}
this.items.splice(i, 0, item);
}

getItems(): T[] {
return this.items;
}
}

泛型约束

基本约束

// 约束 T 必须有 length 属性
interface Lengthwise {
length: number;
}

function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}

logLength("hello"); // 5
logLength([1, 2, 3, 4, 5]); // 5
logLength({ length: 10 }); // 10

// logLength(123); // 错误:number 没有 length 属性

解释:使用 extends 关键字约束泛型类型必须满足某些条件。

在泛型约束中使用类型参数

// 获取对象的某个属性
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

const user = {
name: "张三",
age: 25,
city: "北京"
};

const name = getProperty(user, "name"); // string
const age = getProperty(user, "age"); // number
// getProperty(user, "gender"); // 错误:不存在 "gender" 属性

使用类类型约束

class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}

class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
}

// 约束 T 必须是 Animal 或其子类
function createInstance<T extends Animal>(ctor: new (...args: any[]) => T, name: string): T {
return new ctor(name);
}

const dog = createInstance(Dog, "旺财");
console.log(dog.name); // "旺财"

多类型参数

多个泛型参数

// 多个类型参数
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}

const p1 = pair("name", "张三"); // [string, string]
const p2 = pair(1, "first"); // [number, string]
const p3 = pair(true, 100); // [boolean, number]

// 对象映射
function mapObject<K extends string, V, R>(
obj: Record<K, V>,
mapper: (value: V, key: K) => R
): Record<K, R> {
const result = {} as Record<K, R>;
for (const key in obj) {
result[key] = mapper(obj[key], key);
}
return result;
}

const numbers = { a: 1, b: 2, c: 3 };
const doubled = mapObject(numbers, (v) => v * 2);
// { a: 2, b: 4, c: 6 }

泛型默认类型

// 默认类型
interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
}

// 使用默认类型
const response1: ApiResponse = {
code: 200,
message: "success",
data: { name: "张三" }
};

// 指定类型
const response2: ApiResponse<string> = {
code: 200,
message: "success",
data: "hello"
};

泛型工具类型

Partial

将类型 T 的所有属性变为可选:

interface User {
name: string;
age: number;
email: string;
}

function updateUser(user: User, updates: Partial<User>): User {
return { ...user, ...updates };
}

const user: User = { name: "张三", age: 25, email: "[email protected]" };
const updated = updateUser(user, { age: 26 });
// { name: "张三", age: 26, email: "[email protected]" }

Required

将类型 T 的所有属性变为必需:

interface Props {
name?: string;
age?: number;
}

const props: Required<Props> = {
name: "张三",
age: 25
// 都必须提供
};

Readonly

将类型 T 的所有属性变为只读:

interface Point {
x: number;
y: number;
}

const point: Readonly<Point> = { x: 10, y: 20 };
// point.x = 15; // 错误:只读属性

Pick

从类型 T 中选取部分属性:

interface User {
id: number;
name: string;
email: string;
password: string;
}

// 只选取 id 和 name
type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string; }

Omit

从类型 T 中排除部分属性:

interface User {
id: number;
name: string;
email: string;
password: string;
}

// 排除 password
type PublicUser = Omit<User, "password">;
// { id: number; name: string; email: string; }

Record

创建键类型为 K,值类型为 T 的对象类型:

type Role = "admin" | "user" | "guest";

const permissions: Record<Role, string[]> = {
admin: ["read", "write", "delete"],
user: ["read", "write"],
guest: ["read"]
};

ReturnType

获取函数类型的返回类型:

function getUser() {
return { name: "张三", age: 25 };
}

type User = ReturnType<typeof getUser>;
// { name: string; age: number; }

Parameters

获取函数类型的参数类型:

function greet(name: string, age: number): string {
return `Hello, ${name}!`;
}

type GreetParams = Parameters<typeof greet>;
// [string, number]

泛型实战示例

通用 API 客户端

interface ApiResponse<T> {
code: number;
data: T;
message: string;
}

class ApiClient {
async get<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
return response.json();
}

async post<T, D>(url: string, data: D): Promise<ApiResponse<T>> {
const response = await fetch(url, {
method: "POST",
body: JSON.stringify(data)
});
return response.json();
}
}

// 使用
const client = new ApiClient();
const users = await client.get<User[]>("/api/users");
const newUser = await client.post<User, CreateUserDto>("/api/users", {
name: "张三",
email: "[email protected]"
});

类型安全的事件发射器

type EventHandler<T = any> = (data: T) => void;

class EventEmitter<EventMap extends Record<string, any>> {
private handlers: Partial<{ [K in keyof EventMap]: EventHandler<EventMap[K]>[] }> = {};

on<K extends keyof EventMap>(event: K, handler: EventHandler<EventMap[K]>): void {
if (!this.handlers[event]) {
this.handlers[event] = [];
}
this.handlers[event]!.push(handler);
}

emit<K extends keyof EventMap>(event: K, data: EventMap[K]): void {
const handlers = this.handlers[event];
if (handlers) {
handlers.forEach(handler => handler(data));
}
}

off<K extends keyof EventMap>(event: K, handler: EventHandler<EventMap[K]>): void {
const handlers = this.handlers[event];
if (handlers) {
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
}
}
}

// 使用
interface MyEvents {
userCreated: { id: number; name: string };
userDeleted: { id: number };
}

const emitter = new EventEmitter<MyEvents>();

emitter.on("userCreated", (data) => {
console.log(`User created: ${data.name}`);
});

emitter.emit("userCreated", { id: 1, name: "张三" });

小结

本章我们学习了 TypeScript 泛型:

  1. 泛型函数和类型推断
  2. 泛型接口和泛型类
  3. 泛型约束
  4. 多类型参数
  5. 泛型默认类型
  6. 内置工具类型
  7. 泛型实战应用

练习

  1. 实现一个泛型函数 first,返回数组的第一个元素
  2. 创建一个泛型 Stack
  3. 实现一个类型安全的 Object.keys 包装函数
  4. 使用泛型实现一个简单的依赖注入容器