TypeScript泛型编程
泛型是TypeScript中最强大的特性之一,它允许我们创建可重用的组件,这些组件可以与多种类型一起工作,而不仅仅是单一类型。本文将深入探讨TypeScript泛型的概念、语法和高级应用。
泛型基础
为什么需要泛型?
在没有泛型的情况下,我们通常需要为不同类型创建多个几乎相同的函数:
typescript
function identityString(arg: string): string {
return arg;
}
function identityNumber(arg: number): number {
return arg;
}
function identityBoolean(arg: boolean): boolean {
return arg;
}
或者使用any
类型,但这会丢失类型信息:
typescript
function identity(arg: any): any {
return arg;
}
// 使用any后,我们失去了类型检查
const num = identity(42); // num的类型是any,而不是number
泛型解决了这个问题,它允许我们保留类型信息:
typescript
function identity<T>(arg: T): T {
return arg;
}
// 显式指定类型参数
const str = identity<string>("hello"); // str的类型是string
// 让TypeScript推断类型参数
const num = identity(42); // num的类型是number
泛型语法
泛型使用尖括号<>
语法,其中包含类型参数:
typescript
// 泛型函数
function example<T>(arg: T): T {
return arg;
}
// 多个类型参数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
// 泛型接口
interface Box<T> {
value: T;
}
// 泛型类
class Container<T> {
private item: T;
constructor(item: T) {
this.item = item;
}
getItem(): T {
return this.item;
}
}
// 泛型类型别名
type Result<T> = {
data: T;
error: string | null;
};
使用泛型
泛型可以在多种场景中使用:
typescript
// 泛型函数
function identity<T>(arg: T): T {
return arg;
}
const strResult = identity("hello");
const numResult = identity(42);
const boolResult = identity(true);
// 泛型接口
interface Pair<T, U> {
first: T;
second: U;
}
const numberStringPair: Pair<number, string> = { first: 1, second: "one" };
const booleanDatePair: Pair<boolean, Date> = { first: true, second: new Date() };
// 泛型类
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];
}
isEmpty(): boolean {
return this.items.length === 0;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2
const stringStack = new Stack<string>();
stringStack.push("hello");
stringStack.push("world");
console.log(stringStack.pop()); // "world"
泛型约束
基本约束
有时我们需要限制泛型类型必须具有特定的属性或方法。这可以通过extends
关键字实现:
typescript
// 定义一个接口作为约束
interface Lengthwise {
length: number;
}
// 限制T必须有length属性
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 现在可以安全访问length属性
return arg;
}
logLength("hello"); // 5
logLength([1, 2, 3]); // 3
logLength({ length: 10, value: 42 }); // 10
// 错误:数字没有length属性
// logLength(42);
使用类型参数约束另一个类型参数
一个类型参数可以被另一个类型参数约束:
typescript
// 确保key是obj的属性
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: "Alice", age: 30 };
console.log(getProperty(person, "name")); // "Alice"
console.log(getProperty(person, "age")); // 30
// 错误:Argument of type '"location"' is not assignable to parameter of type '"name" | "age"'
// getProperty(person, "location");
泛型约束中使用类型参数
我们可以声明一个类型参数,该参数被另一个类型参数约束:
typescript
function createInstance<T, U extends new () => T>(Constructor: U): T {
return new Constructor();
}
class Animal {
numLegs: number = 4;
}
const animal = createInstance(Animal);
console.log(animal.numLegs); // 4
默认类型参数
TypeScript允许为泛型类型参数指定默认值:
typescript
// 带默认值的泛型接口
interface ApiResponse<T = any> {
data: T;
status: number;
message: string;
}
// 不指定类型参数时使用默认值
const response: ApiResponse = {
data: { id: 1, name: "Product" },
status: 200,
message: "Success"
};
// 指定类型参数
interface User {
id: number;
name: string;
}
const userResponse: ApiResponse<User> = {
data: { id: 1, name: "Alice" },
status: 200,
message: "User found"
};
高级泛型模式
泛型工厂
泛型工厂是一种创建特定类型实例的函数:
typescript
// 泛型工厂函数
function createFactory<T>(initialValue: T): {
get: () => T;
set: (value: T) => void;
} {
let value = initialValue;
return {
get: () => value,
set: (newValue: T) => { value = newValue; }
};
}
// 字符串工厂
const stringFactory = createFactory("hello");
console.log(stringFactory.get()); // "hello"
stringFactory.set("world");
console.log(stringFactory.get()); // "world"
// 数字工厂
const numberFactory = createFactory(42);
console.log(numberFactory.get()); // 42
numberFactory.set(100);
console.log(numberFactory.get()); // 100
// 对象工厂
interface User {
name: string;
age: number;
}
const userFactory = createFactory<User>({ name: "Alice", age: 30 });
console.log(userFactory.get()); // { name: "Alice", age: 30 }
userFactory.set({ name: "Bob", age: 25 });
console.log(userFactory.get()); // { name: "Bob", age: 25 }
条件类型
条件类型允许我们基于类型关系创建新的类型:
typescript
// 基本条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// 分配条件类型
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumberArray = ToArray<string | number>; // string[] | number[]
// 条件类型中的推断
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function greeting(): string {
return "Hello";
}
type GreetingReturnType = ReturnType<typeof greeting>; // string
// 高级条件类型
type NonNullable<T> = T extends null | undefined ? never : T;
type StringOrNumber = string | number | null | undefined;
type NonNullStringOrNumber = NonNullable<StringOrNumber>; // string | number
映射类型
映射类型允许我们基于旧类型创建新类型,通过转换每个属性:
typescript
// 基本映射类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
// 等同于:{ readonly name: string; readonly age: number; }
// 可选映射类型
type Partial<T> = {
[P in keyof T]?: T[P];
};
type PartialUser = Partial<User>;
// 等同于:{ name?: string; age?: number; }
// 记录映射类型
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type UserRoles = Record<"admin" | "user" | "guest", { permissions: string[] }>;
// 等同于:{ admin: { permissions: string[] }; user: { permissions: string[] }; guest: { permissions: string[] }; }
// 高级映射类型
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type NameOnly = Pick<User, "name">;
// 等同于:{ name: string; }
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type WithoutAge = Omit<User, "age">;
// 等同于:{ name: string; }
泛型递归类型
递归类型是指在其定义中引用自身的类型:
typescript
// 递归类型:JSON值
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
const json: JSONValue = {
name: "Alice",
age: 30,
isAdmin: false,
address: {
street: "123 Main St",
city: "Anytown"
},
hobbies: ["reading", "swimming"]
};
// 递归类型:嵌套对象
type NestedObject<T> = {
value: T;
children?: NestedObject<T>[];
};
const nested: NestedObject<string> = {
value: "root",
children: [
{ value: "child1" },
{
value: "child2",
children: [
{ value: "grandchild" }
]
}
]
};
// 递归类型:深度部分
type DeepPartial<T> = T extends object ? {
[P in keyof T]?: DeepPartial<T[P]>;
} : T;
interface Department {
name: string;
employees: {
id: number;
name: string;
address: {
street: string;
city: string;
zipCode: string;
}
}[];
}
// 所有属性都是可选的,包括嵌套属性
const partialDept: DeepPartial<Department> = {
name: "Engineering",
employees: [
{
id: 1,
address: {
city: "San Francisco"
// street和zipCode是可选的
}
}
]
};
泛型最佳实践
命名约定
泛型类型参数通常使用单个大写字母或有描述性的名称:
typescript
// 单个字母(常见约定)
function identity<T>(arg: T): T {
return arg;
}
// 描述性名称(更清晰)
function createPair<First, Second>(first: First, second: Second): [First, Second] {
return [first, second];
}
// 常见泛型命名约定
// T: 类型参数的默认名称
// K: 键类型,通常是对象的键
// V: 值类型,通常与K一起使用
// E: 元素类型,用于数组或集合
// P: 属性类型
// R: 返回类型
// S, U, V等: 额外的类型参数
// 示例
function get<O extends object, K extends keyof O>(obj: O, key: K): O[K] {
return obj[key];
}
function map<T, U>(array: T[], fn: (item: T) => U): U[] {
return array.map(fn);
}
何时使用泛型
泛型最适合以下场景:
- 当函数或类需要处理多种类型时
- 当你需要保留类型信息时
- 当你需要在多个地方使用相同的类型时
typescript
// 适合使用泛型:处理多种类型并保留类型信息
function first<T>(array: T[]): T | undefined {
return array[0];
}
// 适合使用泛型:类型安全的数据容器
class Result<T, E = Error> {
private constructor(
private readonly value?: T,
private readonly error?: E
) {}
static success<T>(value: T): Result<T, never> {
return new Result(value, undefined);
}
static failure<E>(error: E): Result<never, E> {
return new Result(undefined, error);
}
isSuccess(): boolean {
return this.error === undefined;
}
isFailure(): boolean {
return this.error !== undefined;
}
getOrThrow(): T {
if (this.isSuccess()) {
return this.value as T;
}
throw this.error;
}
getOrElse(defaultValue: T): T {
return this.isSuccess() ? (this.value as T) : defaultValue;
}
}
// 使用示例
function divide(a: number, b: number): Result<number, string> {
if (b === 0) {
return Result.failure("Division by zero");
}
return Result.success(a / b);
}
const result = divide(10, 2);
if (result.isSuccess()) {
console.log(result.getOrThrow()); // 5
} else {
console.error("Error occurred");
}
const errorResult = divide(10, 0);
console.log(errorResult.getOrElse(0)); // 0
避免过度使用泛型
虽然泛型很强大,但过度使用会使代码难以理解:
typescript
// 过度使用泛型的例子
function processData<T, U, V, W, X>(
data: T,
transformer: (input: T) => U,
validator: (input: U) => V extends boolean ? W : X
): V extends boolean ? W : X {
const transformed = transformer(data);
return validator(transformed) as any;
}
// 更好的方式:拆分成更小、更专注的函数
function transform<T, U>(data: T, transformer: (input: T) => U): U {
return transformer(data);
}
function validate<T>(data: T, validator: (input: T) => boolean): boolean {
return validator(data);
}
function processData<T, U>(
data: T,
transformer: (input: T) => U,
validator: (input: U) => boolean
): U | null {
const transformed = transformer(data);
return validator(transformed) ? transformed : null;
}
实际应用示例
类型安全的API客户端
typescript
// 类型安全的API客户端
interface ApiClient {
get<T>(url: string): Promise<T>;
post<T, U>(url: string, data: T): Promise<U>;
put<T, U>(url: string, data: T): Promise<U>;
delete<T>(url: string): Promise<T>;
}
class HttpClient implements ApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
async get<T>(url: string): Promise<T> {
const response = await fetch(`${this.baseUrl}${url}`);
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json();
}
async post<T, U>(url: string, data: T): Promise<U> {
const response = await fetch(`${this.baseUrl}${url}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json();
}
async put<T, U>(url: string, data: T): Promise<U> {
const response = await fetch(`${this.baseUrl}${url}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json();
}
async delete<T>(url: string): Promise<T> {
const response = await fetch(`${this.baseUrl}${url}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
return response.json();
}
}
// 使用示例
interface User {
id: number;
name: string;
email: string;
}
interface CreateUserDto {
name: string;
email: string;
password: string;
}
const apiClient = new HttpClient('https://api.example.com');
// 获取用户列表
async function getUsers(): Promise<User[]> {
return apiClient.get<User[]>('/users');
}
// 创建新用户
async function createUser(user: CreateUserDto): Promise<User> {
return apiClient.post<CreateUserDto, User>('/users', user);
}
// 更新用户
async function updateUser(id: number, user: Partial<User>): Promise<User> {
return apiClient.put<Partial<User>, User>(`/users/${id}`, user);
}
// 删除用户
async function deleteUser(id: number): Promise<void> {
return apiClient.delete<void>(`/users/${id}`);
}
状态管理
typescript
// 类型安全的状态管理
type Listener<T> = (state: T) => void;
class Store<T> {
private state: T;
private listeners: Listener<T>[] = [];
constructor(initialState: T) {
this.state = initialState;
}
getState(): T {
return this.state;
}
setState(newState: Partial<T>): void {
this.state = { ...this.state, ...newState };
this.notify();
}
subscribe(listener: Listener<T>): () => void {
this.listeners.push(listener);
// 返回取消订阅函数
return () => {
const index = this.listeners.indexOf(listener);
if (index > -1) {
this.listeners.splice(index, 1);
}
};
}
private notify(): void {
this.listeners.forEach(listener => listener(this.state));
}
}
// 使用示例
interface AppState {
user: {
name: string;
isLoggedIn: boolean;
};
theme: 'light' | 'dark';
notifications: string[];
}
const initialState: AppState = {
user: {
name: '',
isLoggedIn: false
},
theme: 'light',
notifications: []
};
const store = new Store<AppState>(initialState);
// 订阅状态变化
const unsubscribe = store.subscribe(state => {
console.log('State changed:', state);
});
// 更新状态
store.setState({
user: {
name: 'Alice',
isLoggedIn: true
}
});
// 更新主题
store.setState({ theme: 'dark' });
// 添加通知
store.setState({
notifications: [...store.getState().notifications, 'New message']
});
// 取消订阅
unsubscribe();
泛型组件(React示例)
tsx
// 泛型React组件
import React, { useState } from 'react';
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string | number;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>
{renderItem(item)}
</li>
))}
</ul>
);
}
// 使用示例
interface User {
id: number;
name: string;
email: string;
}
function UserList() {
const users: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
{ id: 3, name: 'Charlie', email: 'charlie@example.com' }
];
return (
<List<User>
items={users}
renderItem={user => (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
)}
keyExtractor={user => user.id}
/>
);
}
// 泛型钩子
function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value: T) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// 使用泛型钩子
function UserSettings() {
interface Settings {
theme: 'light' | 'dark';
fontSize: number;
notifications: boolean;
}
const [settings, setSettings] = useLocalStorage<Settings>('user-settings', {
theme: 'light',
fontSize: 16,
notifications: true
});
return (
<div>
<h2>User Settings</h2>
<label>
Theme:
<select
value={settings.theme}
onChange={e => setSettings({ ...settings, theme: e.target.value as 'light' | 'dark' })}
>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</label>
<label>
Font Size:
<input
type="number"
value={settings.fontSize}
onChange={e => setSettings({ ...settings, fontSize: parseInt(e.target.value) })}
/>
</label>
<label>
Notifications:
<input
type="checkbox"
checked={settings.notifications}
onChange={e => setSettings({ ...settings, notifications: e.target.checked })}
/>
</label>
</div>
);
}
总结
泛型是TypeScript中最强大的特性之一,它允许我们编写灵活、可重用且类型安全的代码。通过掌握泛型的基础知识、约束、条件类型和映射类型,你可以创建适应各种场景的通用组件。
记住,泛型的目标是提高代码的可重用性,同时保持类型安全。在实际应用中,合理使用泛型可以显著提高代码质量和开发效率。随着你对泛型的深入理解,你将能够解决越来越复杂的类型问题,并创建更加健壮的应用程序。
贡献者
huoshan