泛型设计模式
泛型设计模式的核心思想
泛型设计模式的核心在于编写可复用的代码组件,这些组件能够处理多种数据类型而不丧失类型安全性。TypeScript中的泛型允许我们创建灵活的函数、类和接口,同时保持严格的类型检查。泛型不是具体类型,而是类型参数,在使用时由开发者指定具体类型。
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("hello"); // 输出类型为string
let output2 = identity<number>(42); // 输出类型为number
泛型约束与默认类型
泛型可以添加约束条件,限制可接受的类型范围。使用extends
关键字可以指定类型参数必须满足的接口或基本类型。同时,TypeScript支持为泛型参数提供默认类型,当调用时未显式指定类型时会使用默认值。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
// 必须传入带有length属性的对象
loggingIdentity({length: 10, value: 3});
// 设置默认类型
interface ApiResponse<T = any> {
data: T;
status: number;
}
const response: ApiResponse<string> = {
data: "success",
status: 200
};
泛型在函数中的应用
函数泛型可能是最常见的应用场景。它们允许我们编写能够处理多种类型输入的函数,同时保持输入和输出之间的类型关系。高阶函数特别适合使用泛型,因为它们经常需要处理各种类型的回调函数。
function map<T, U>(array: T[], transform: (item: T) => U): U[] {
return array.map(transform);
}
const numbers = [1, 2, 3];
const strings = map(numbers, (n) => n.toString()); // string[]
function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: number;
return (...args: Parameters<T>) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}
泛型在类中的应用
类泛型允许我们创建可重用的类定义,这些类可以处理不同的数据类型。集合类(如栈、队列、链表)是泛型类的典型用例,因为它们本质上需要存储各种类型的元素。
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);
const stringStack = new Stack<string>();
stringStack.push("hello");
泛型在接口中的应用
接口泛型提供了一种定义灵活契约的方式。它们特别适合描述API响应、数据访问层或服务接口,因为这些场景通常需要处理各种数据类型但保持一致的形状。
interface Repository<T, ID> {
findById(id: ID): Promise<T | null>;
save(entity: T): Promise<T>;
delete(id: ID): Promise<void>;
}
class UserRepository implements Repository<User, string> {
async findById(id: string): Promise<User | null> {
// 实现查找逻辑
}
// 其他方法实现...
}
interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
perPage: number;
}
async function fetchUsers(): Promise<PaginatedResponse<User>> {
// 获取用户数据
}
条件类型与映射类型
TypeScript的高级类型特性如条件类型和映射类型与泛型结合使用时特别强大。条件类型允许基于类型关系选择类型,而映射类型可以转换现有类型的属性。
type NonNullable<T> = T extends null | undefined ? never : T;
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// 使用示例
interface User {
id: string;
name: string;
email: string;
}
type UserPreview = Pick<User, 'id' | 'name'>;
泛型工具类型实践
TypeScript内置了许多实用的泛型工具类型,合理使用它们可以显著减少重复的类型定义工作。这些工具类型通常结合了条件类型和映射类型的特性。
// 从T中排除可以赋值给U的类型
type Exclude<T, U> = T extends U ? never : T;
// 从T中提取可以赋值给U的类型
type Extract<T, U> = T extends U ? T : never;
// 获取函数返回类型
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
// 获取构造函数实例类型
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
// 实际应用
function getUser(): Promise<User> {
return Promise.resolve({ id: '1', name: 'John' });
}
type UserPromise = ReturnType<typeof getUser>; // Promise<User>
泛型与React组件
在React开发中,泛型组件可以创建高度可复用的UI元素。特别是对于列表、表格、表单等需要处理各种数据类型的组件,泛型提供了完美的解决方案。
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function GenericList<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item)}</li>
))}
</ul>
);
}
// 使用示例
const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
<UserList
items={users}
renderItem={(user) => <span>{user.name}</span>}
/>
泛型类型推断的最佳实践
TypeScript的类型推断系统非常强大,通常不需要显式指定泛型类型参数。理解类型推断的工作原理可以帮助我们编写更简洁的代码,同时保持类型安全。
// 不需要显式指定类型参数
let array = [1, 2, 3]; // 推断为number[]
let tuple = [1, "hello"] as const; // 推断为readonly [1, "hello"]
// 函数参数类型推断
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const result = pair(1, "hello"); // 推断为[number, string]
// 回调函数参数类型推断
const users = [{ name: "Alice" }, { name: "Bob" }];
const names = users.map(user => user.name); // 推断为string[]
泛型与函数重载的结合
当泛型与函数重载结合使用时,可以创建既灵活又具有精确类型签名的API。这种模式在库开发中特别有用,可以为不同的参数组合提供不同的返回类型。
function parseInput<T extends string | number>(input: T): T extends string ? Date : number;
function parseInput(input: string | number): Date | number {
if (typeof input === 'string') {
return new Date(input);
} else {
return input * 2;
}
}
const date = parseInput("2023-01-01"); // Date
const number = parseInput(42); // number
泛型在状态管理中的应用
状态管理库经常使用泛型来定义存储的结构和操作。Redux的reducer、selector和action创建函数都可以从泛型中受益,确保类型安全贯穿整个应用状态。
interface Action<T extends string, P> {
type: T;
payload: P;
}
function createAction<T extends string, P>(type: T, payload: P): Action<T, P> {
return { type, payload };
}
// 使用示例
const addTodo = createAction('ADD_TODO', { text: 'Learn TypeScript' });
interface State {
todos: Array<{ text: string; completed: boolean }>;
}
function reducer(state: State, action: Action<string, any>): State {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, { text: action.payload.text, completed: false }]
};
// 其他case...
}
}
泛型与高阶组件模式
在React中,高阶组件(HOC)经常使用泛型来保持被包装组件的props类型。这种模式确保了类型信息不会在组件组合过程中丢失。
function withLoading<T extends object>(
WrappedComponent: React.ComponentType<T>
) {
return function WithLoading(props: T & { isLoading: boolean }) {
if (props.isLoading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...props} />;
};
}
// 使用示例
interface UserProfileProps {
user: User;
className?: string;
}
const UserProfile: React.FC<UserProfileProps> = ({ user }) => (
<div>{user.name}</div>
);
const UserProfileWithLoading = withLoading(UserProfile);
// 现在UserProfileWithLoading的props包括原来的UserProfileProps加上isLoading
<UserProfileWithLoading user={user} isLoading={true} />
泛型类型守卫与类型谓词
泛型可以与类型守卫结合使用,创建可重用的类型判断函数。这些函数不仅执行运行时检查,还帮助TypeScript编译器缩小类型范围。
function isArrayOf<T>(
arr: unknown,
check: (item: unknown) => item is T
): arr is T[] {
return Array.isArray(arr) && arr.every(check);
}
function isString(item: unknown): item is string {
return typeof item === 'string';
}
// 使用示例
const data: unknown = ["a", "b", "c"];
if (isArrayOf(data, isString)) {
// 在此块中,data被推断为string[]
data.map(s => s.toUpperCase());
}
泛型与索引访问类型
索引访问类型与泛型结合使用时,可以创建基于对象键的动态类型。这种模式在需要根据属性名访问或操作对象类型时特别有用。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// 使用示例
const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // string
const age = getProperty(user, "age"); // number
// 更复杂的例子
type EventMap = {
click: MouseEvent;
scroll: Event;
keypress: KeyboardEvent;
};
function triggerEvent<T extends keyof EventMap>(
eventName: T,
handler: (event: EventMap[T]) => void
): void {
// 模拟事件触发
const event = {} as EventMap[T];
handler(event);
}
triggerEvent("click", (e) => {
// e被推断为MouseEvent
console.log(e.clientX);
});
泛型与Promise组合模式
在处理异步操作时,泛型可以帮助我们保持Promise链中的类型一致性。特别是在构建API客户端或数据访问层时,这种模式可以确保从请求到响应整个流程的类型安全。
interface ApiResponse<T> {
data: T;
status: number;
headers: Record<string, string>;
}
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
const data = await response.json();
return {
data,
status: response.status,
headers: Object.fromEntries(response.headers.entries())
};
}
// 使用示例
interface User {
id: number;
name: string;
}
async function getUser() {
const response = await fetchData<User[]>("/api/users");
// response.data是User[]类型
return response.data;
}
泛型与装饰器模式
在TypeScript中,装饰器可以与泛型结合使用,创建可重用的装饰器逻辑。这种模式在需要为不同类添加相同行为时特别有用,同时保持被装饰类的类型信息。
function LogClass<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
super(...args);
console.log(`Instance of ${constructor.name} created`);
}
};
}
// 使用示例
@LogClass
class UserService {
constructor(private repository: UserRepository) {}
}
// 当实例化UserService时,会打印日志
const service = new UserService(new UserRepository());
泛型与可变元组类型
TypeScript 4.0引入的可变元组类型与泛型结合使用时,可以创建极其灵活的函数签名。这种模式特别适合需要处理可变参数列表的高阶函数。
function curry<T extends any[], U, R>(
fn: (...args: [...T, U]) => R,
...initialArgs: T
): (arg: U) => R {
return (arg: U) => fn(...initialArgs, arg);
}
// 使用示例
function add(a: number, b: number): number {
return a + b;
}
const addFive = curry(add, 5);
const result = addFive(3); // 8
// 更复杂的例子
function compose<T extends any[], U, V>(
f: (x: U) => V,
g: (...args: T) => U
): (...args: T) => V {
return (...args) => f(g(...args));
}
const toUpperCase = (x: string) => x.toUpperCase();
const join = (separator: string, ...strings: string[]) => strings.join(separator);
const upperJoin = compose(toUpperCase, join);
const result2 = upperJoin("-", "a", "b", "c"); // "A-B-C"
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn