TypeScript 的泛型(Generics)
#typescript
#R1
目录
1. 泛型基础
泛型是一种在定义函数、接口或类时不预先指定具体类型,而在使用时再指定类型的特性。
个人理解是:类比一个函数,可以传入参数,来确定这个接口、函数、或类的具体类型
1.1. 基本语法
泛型使用尖括号 <T>
来定义,其中 T
是一个类型变量:
// 泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用方式 1
let output1 = identity<string>("hello"); // 显式指定类型
let output2 = identity("hello"); // 类型推断
// 使用方式 2
let output1 = identity<string>("myString");
let output2 = identity(123); // 类型推断为 number
1.2. 泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}
let myIdentity: GenericIdentityFn<number> = (arg: number): number => {
return arg;
};
interface Collection<T> {
add(item: T): void;
remove(item: T): void;
getItems(): T[];
}
2. 泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
constructor(zero: T, addFn: (x: T, y: T) => T) {
this.zeroValue = zero;
this.add = addFn;
}
}
// 使用示例
const numCalculator = new GenericNumber<number>(0, (x, y) => x + y);
const strCalculator = new GenericNumber<string>('', (x, y) => x + y);
3. 泛型约束
3.1. 使用 extends
关键字来约束泛型
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): void {
console.log(arg.length);
}
// 正确
logLength("hello"); // 字符串有 length 属性
logLength([1, 2, 3]); // 数组有 length 属性
logLength({ length: 10 }); // 对象有 length 属性
// 错误
// logLength(3); // 数字没有 length 属性
3.2. 在泛型约束中使用类型参数
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
let obj = { a: 1, b: 2, c: 3 };
getProperty(obj, "a"); // 正确
// getProperty(obj, "z"); // 错误:z 不是 obj 的属性
let x = { a: 1, b: 2, c: 3 };
getProperty(x, "a"); // 正确
getProperty(x, "d"); // 错误:参数 'd' 不存在于 'a' | 'b' | 'c' 中
4. 多个类型参数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const p1 = pair<string, number>("hello", 42);
const p2 = pair("world", true); // 类型推断
// 在类中使用多个类型参数
class KeyValuePair<TKey, TValue> {
constructor(
public key: TKey,
public value: TValue
) {}
}
5. 常用的泛型工具类型
TypeScript 提供了几个常用的泛型工具类型
// Partial - 使所有属性可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
// Readonly - 使所有属性只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// Pick - 从类型中选择部分属性
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Record - 创建具有特定类型属性的类型
type Record<K extends keyof any, T> = {
[P in K]: T;
};
下面分开介绍
5.1. Partial<T>
将类型的所有属性变为可选:
interface Todo {
title: string;
description: string;
}
type PartialTodo = Partial<Todo>;
// 等价于:
// {
// title?: string;
// description?: string;
// }
5.2. Record<K,T>
创建一个键类型为 K,值类型为 T 的对象类型:
type PageInfo = {
title: string;
}
type Page = "home" | "about" | "contact";
const nav: Record<Page, PageInfo> = {
home: { title: "Home" },
about: { title: "About" },
contact: { title: "Contact" }
};
5.3. Pick<T,K>
和 Omit<T,K>
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
// 等价于:{ title: string; completed: boolean; }
type TodoInfo = Omit<Todo, "completed">;
// 等价于:{ title: string; description: string; }
6. 实际应用示例
6.1. 泛型组件(React 示例)
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>(props: ListProps<T>) {
return (
<div>
{props.items.map((item, index) => (
<div key={index}>
{props.renderItem(item)}
</div>
))}
</div>
);
}
6.2. 泛型 API 请求
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
return response.json();
}
interface User {
id: number;
name: string;
}
// 使用
const user = await fetchData<User>('/api/user');
这个很常用!!!
7. 最佳实践
7.1. 命名约定
- T 用于表示第一个类型参数
- K 通常用于表示对象的键类型
- V 用于表示对象的值类型
- E 用于表示元素类型
- 使用单个大写字母作为简单泛型类型参数(T, U, V)
- 对于更复杂的场景,使用有描述性的名称(TKey, TValue, TEntity)
function map<TInput, TOutput>(
array: TInput[],
fn: (item: TInput) => TOutput
): TOutput[] {
return array.map(fn);
}
7.2. 默认类型参数
interface DefaultGeneric<T = string> {
value: T;
}
const stringValue: DefaultGeneric = { value: "hello" };
const numberValue: DefaultGeneric<number> = { value: 42 };
7.3. 泛型约束的组合
interface HasId {
id: number;
}
interface HasName {
name: string;
}
function processThing<T extends HasId & HasName>(thing: T) {
console.log(thing.id, thing.name);
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
8. 高级用法
8.1. 条件类型与泛型
type NonNullable<T> = T extends null | undefined ? never : T;
type ExtractType<T> = T extends string
? 'string'
: T extends number
? 'number'
: 'other';
8.2. 映射类型与泛型
type Optional<T> = {
[K in keyof T]?: T[K];
};
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
9. 最后
泛型是 TypeScript 中最强大的特性之一,它可以:
- 提供类型安全性
- 增加代码重用性
- 使代码更加灵活
- 减少重复代码
通过合理使用泛型,我们可以编写出更加通用和类型安全的代码。在实际开发中,泛型经常用于:
- 容器类的实现
- 工具函数的编写
- API 请求的类型定义
- UI 组件的属性定义