泛型(Generics)是 TypeScript 中非常重要的特性,它允许我们创建可重用的组件,这些组件可以支持多种类型,而不会丢失类型安全性。
一、泛型基础
1. 基本概念
泛型允许我们在定义函数、接口或类时不预先指定具体类型,而是在使用时再指定类型。
// 不使用泛型
function identity(arg: number): number {
return arg;
}
// 使用泛型
function identity<T>(arg: T): T {
return arg;
}
2. 泛型变量
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array有length属性
return arg;
}
二、泛型类型
1. 泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 两种调用方式
let output1 = identity<string>("myString"); // 显式指定类型
let output2 = identity("myString"); // 类型推断
2. 泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
3. 泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = (x, y) => x + y;
三、泛型约束
1. 基本约束
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
2. 在泛型约束中使用类型参数
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3 };
getProperty(x, "a"); // 正确
getProperty(x, "m"); // 错误:m不是x的属性
四、高级泛型技巧
1. 泛型默认类型
interface DefaultGeneric<T = string> {
value: T;
}
const a: DefaultGeneric = { value: "hello" }; // T默认为string
const b: DefaultGeneric<number> = { value: 123 };
2. 条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
3. 映射类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
五、常见内置泛型工具
1. Partial<T>
将所有属性变为可选:
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
2. Required<T>
将所有属性变为必选:
interface Props {
a?: number;
b?: string;
}
const obj: Required<Props> = { a: 5, b: "hello" };
3. Readonly<T>
将所有属性变为只读:
interface Todo {
title: string;
}
const todo: Readonly<Todo> = { title: "Delete inactive users" };
todo.title = "Hello"; // 错误:无法分配到"title",因为它是只读属性
4. Record<K, T>
构造一个类型,其属性键为K,属性值为T:
interface CatInfo {
age: number;
breed: string;
}
type CatName = "miffy" | "boris" | "mordred";
const cats: Record<CatName, CatInfo> = {
miffy: { age: 10, breed: "Persian" },
boris: { age: 5, breed: "Maine Coon" },
mordred: { age: 16, breed: "British Shorthair" },
};
5. Pick<T, K>
从类型T中选取一组属性K:
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
6. Omit<T, K>
从类型T中排除一组属性K:
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Omit<Todo, "description">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
六、实际应用示例
1. API响应类型
interface ApiResponse<T = any> {
code: number;
data: T;
message: string;
}
// 用户数据
interface User {
id: number;
name: string;
}
// 使用
async function getUser(): Promise<ApiResponse<User>> {
const response = await fetch("/api/user");
return response.json();
}
2. 高阶组件
function withLoading<T>(WrappedComponent: React.ComponentType<T>) {
return (props: T & { isLoading: boolean }) => {
return props.isLoading ? <div>Loading...</div> : <WrappedComponent {...props} />;
};
}
3. 状态管理
interface StoreState<T> {
data: T[];
loading: boolean;
error: Error | null;
}
function createStore<T>(initialData: T[]): StoreState<T> {
return {
data: initialData,
loading: false,
error: null,
};
}
七、常见问题
1. 什么时候使用泛型?
当函数、接口或类需要处理多种数据类型时
当需要保持输入与输出类型一致时
当需要基于已有类型创建更复杂的类型时
2. 泛型与any的区别
any 放弃了类型检查
泛型保持了类型约束,提供了更好的类型安全性
3. 如何限制泛型范围?
使用 extends 关键字:
function process<T extends string | number>(value: T): T {
// ...
}
泛型是 TypeScript 中非常强大的特性,合理使用可以大大提高代码的可重用性和类型安全性。
405

被折叠的 条评论
为什么被折叠?



