𝑻𝒆𝒏𝑪𝒍𝒂𝒘正在头脑风暴···
𝑻𝒆𝒏𝑲𝒊𝑺𝒆𝒀𝒂の𝑨𝒈𝒆𝒏𝒕助手
𝑻𝒆𝒏-𝒇𝒍𝒂𝒔𝒉

TypeScript 泛型编程实战

TypeScript 的泛型系统是其最强大的特性之一。本文将全面介绍泛型编程的核心概念、高级技巧和实战应用。

一、泛型基础

1.1 什么是泛型?

泛型允许我们在定义函数、接口或类时使用类型参数,使这些类型可以在使用时确定。

1.2 基础泛型函数

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

const output1 = identity<string>("hello"); // "hello"
const output2 = identity<number>(123); // 123

// 使用泛型
function identity<T>(arg: T): T {
return arg;
}

const output3 = identity("hello"); // 推断为 string
const output4 = identity(123); // 推断为 number

1.3 多个类型参数

// 多个类型参数
function swap<T, U>(first: T, second: U): [U, T] {
return [second, first];
}

const result = swap("hello", 123);
// result 的类型是 [number, string]

二、泛型约束

2.1 基础约束

// 指定类型参数的约束
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}

console.log(getLength("hello")); // 5
console.log(getLength([1, 2, 3])); // 3

// getLength({ name: "张三" }); // Error: 对象没有 length 属性

2.2 多个约束

// 多个约束
interface Lengthwise {
length: number;
}

function getLength<T extends Lengthwise>(arg: T): number {
return arg.length;
}

getLength("hello"); // 5
getLength([1, 2, 3]); // 3

2.3 约束和类型参数

// 约束和类型参数
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

const user = {
name: "张三",
age: 25,
email: "zhang@example.com"
};

console.log(getProperty(user, "name")); // "张三"
console.log(getProperty(user, "age")); // 25
// getProperty(user, "invalid"); // Error: 类型错误

三、泛型接口和类

3.1 泛型接口

// 泛型接口
interface Container<T> {
value: T;
}

const numberContainer: Container<number> = { value: 42 };
const stringContainer: Container<string> = { value: "hello" };

3.2 泛型类

// 泛型类
class Box<T> {
private value: T;

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

getValue(): T {
return this.value;
}

setValue(value: T): void {
this.value = value;
}
}

const numberBox = new Box<number>(100);
const stringBox = new Box<string>("hello");

console.log(numberBox.getValue()); // 100
console.log(stringBox.getValue()); // "hello"

3.3 泛型方法和构造函数

// 泛型方法和构造函数
class ApiClient<T> {
private data: T[] = [];

add(item: T): void {
this.data.push(item);
}

getAll(): T[] {
return this.data;
}
}

const apiClient = new ApiClient<{ id: number, name: string }>();
apiClient.add({ id: 1, name: "张三" });
apiClient.add({ id: 2, name: "李四" });

console.log(apiClient.getAll()); // [{ id: 1, name: "张三" }, { id: 2, name: "李四" }]

四、高级泛型

4.1 映射类型

// 映射类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

type Partial<T> = {
[P in keyof T]?: T[P];
};

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

type ReadonlyUser = Readonly<User>; // 只读
type PartialUser = Partial<User>; // 可选

4.2 条件类型

// 条件类型
type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">; // true
type B = IsString<number>; // false

// 类型谓词
function isString(value: unknown): value is string {
return typeof value === 'string';
}

if (isString(value)) {
// 这里可以确定 value 是 string
console.log(value.toUpperCase());
}

4.3 条件映射

// 条件映射
type Exclude<T, U> = T extends U ? never : T;

type T = "a" | "b" | "c" | "d";
type B = Exclude<T, "b" | "d">; // "a" | "c"

4.4 映射约束

// 映射约束
type WithRequired<T, K extends keyof T> = T & {
[P in K]-?: T[P]; // 强制必填
};

type WithOptional<T, K extends keyof T> = T & {
[P in K]?: T[P]; // 强制可选
};

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

type RequiredUser = WithRequired<User, "age" | "email">;
// RequiredUser: { name: string; age: number; email: string }

4.5 模板字面量类型

// 模板字面量类型
type EventName = `on${Capitalize<string>}`;

type ClickEvent = EventName<'click'>; // "Click"
type ChangeEvent = EventName<'change'>; // "Change"

4.6 模式匹配

// 模式匹配
type Extract<T, U> = T extends U ? T : never;

type T = "a" | "b" | "c" | "d";
type B = Extract<T, "a" | "c">; // "a" | "c"

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

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

type WithoutPassword = Omit<User, "password">;
// WithoutPassword: { name: string; age: number; email: string }

五、泛型实战

5.1 管道函数

// 管道函数
function pipe<T>(fn1: (arg: T) => T, fn2: (arg: T) => T): (arg: T) => T {
return (arg: T): T => {
return fn2(fn1(arg));
};
}

const toUpper = (str: string): string => str.toUpperCase();
const addExclamation = (str: string): string => `${str}!`;

const transform = pipe(toUpper, addExclamation);
console.log(transform("hello")); // "HELLO!"

5.2 Promise 包装器

// Promise 包装器
function promiseWrapper<T>(promise: Promise<T>): Promise<T> {
return promise
.then(result => ({ data: result, error: null } as const))
.catch(error => ({ data: null, error } as const));
}

type Result<T> = {
data: T | null;
error: Error | null;
};

async function getData(): Promise<Result<User>> {
const result = await promiseWrapper(fetch('/api/data'));
if (result.error) {
throw result.error;
}
return result.data;
}

5.3 对比函数

// 对比函数
function compare<T>(a: T, b: T): number {
return a > b ? 1 : a < b ? -1 : 0;
}

const numbers = [1, 2, 3, 4, 5];
numbers.sort(compare); // [1, 2, 3, 4, 5]

5.4 路由参数类型

// 路由参数类型
type RouteParams = {
'/users': { id: string };
'/posts': { id: string; userId: string };
'/search': { q: string; page?: number };
};

type GetRouteParam<Path extends keyof RouteParams, Param extends keyof RouteParams[Path]> =
RouteParams[Path][Param];

type UserParams = GetRouteParam<'/users', 'id'>; // string
type PostParams = GetRouteParam<'/posts', 'id'>; // string

5.5 状态管理

// 状态管理
interface State<T> {
data: T;
loading: boolean;
error: Error | null;
}

function createState<T>(initial: T): State<T> {
return {
data: initial,
loading: false,
error: null,
};
}

const userState = createState({ name: "张三", age: 25 });

5.6 路由匹配

// 路由匹配
type Route = {
path: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
handler: (req: Request) => Response;
};

type Routes = Route[];

function matchRoute<Routes extends Route[]>(url: string, routes: Routes): Route | null {
const [path, ...rest] = url.split('/').filter(Boolean);

for (const route of routes) {
const routePath = route.path.split('/').filter(Boolean);

if (routePath[0] === path) {
return route;
}
}

return null;
}

六、泛型约束的进阶技巧

6.1 模板字面量类型

// 模板字面量类型
type EventName = `on${Capitalize<string>}`;

type ClickEvent = EventName<'click'>; // "Click"
type ChangeEvent = EventName<'change'>; // "Change"

6.2 条件类型

// 条件类型
type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">; // true
type B = IsString<number>; // false

6.3 映射类型

// 映射类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

type Partial<T> = {
[P in keyof T]?: T[P];
};

6.4 工具类型

// 工具类型
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

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

type UserOnly = Omit<User, "id" | "age">;
// UserOnly: { name: string; email: string }

七、泛型的最佳实践

7.1 合理使用泛型

// 好的做法:在需要的地方使用泛型
function identity<T>(arg: T): T {
return arg;
}

// 不好的做法:过度使用泛型
function genericFunction<T>(arg1: T, arg2: T): T {
return arg1;
}

7.2 提供默认值

// 好的做法:提供默认值
function createContainer<T>(value?: T): Container<T> {
return { value: value || null };
}

// 不好的做法:没有默认值
function createContainer<T>(value: T): Container<T> {
return { value };
}

7.3 清晰的类型定义

// 好的做法:清晰的类型定义
function getUser<T extends { id: number }>(id: number): Promise<T | null> {
// ...
}

// 不好的做法:不清晰的类型定义
function getUser<T>(id: number): Promise<T> {
// ...
}

八、常见错误和解决方案

8.1 类型推断问题

// 不好的做法:类型推断不正确
function map<T>(arr: T[], fn: (item: T) => T): T[] {
return arr.map(fn);
}

// 好的做法:明确返回类型
function map<T, R>(arr: T[], fn: (item: T) => R): R[] {
return arr.map(fn);
}

8.2 约束不充分

// 不好的做法:约束不充分
function getProperty<T>(obj: T, key: string) {
return obj[key];
}

// 好的做法:充分约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

九、总结

9.1 泛型的核心要点

  1. 类型参数:在定义时使用,在实例化时确定
  2. 类型推断:自动推断类型参数
  3. 泛型约束:限制类型参数的范围
  4. 工具类型:提供常见的类型转换

9.2 常用工具类型

  • Partial<T>:部分类型
  • Readonly<T>:只读类型
  • Pick<T, K>:选择类型
  • Omit<T, K>:排除类型
  • Record<K, T>:记录类型
  • Exclude<T, U>:排除类型
  • Extract<T, U>:提取类型

掌握泛型,写出更强大、更安全的 TypeScript 代码!