泛型编程完整体系
一句话概述:泛型约束、默认值、const 类型参数、高阶泛型模式——用泛型写出类型安全且可复用的代码
什么是泛型?
定义:泛型(Generics)是参数化类型,允许在定义函数、类、接口时不预先指定具体类型,而是在使用时由调用方或编译器推断确定。
涉及场景:
- 通用数据结构:
Array<T>、Map<K, V>、Promise<T> - 工具函数:
pick、omit、merge等通用操作 - React 组件:泛型 Props、泛型 Hooks
- API 层封装:类型安全的请求函数
作用:
- 在保持类型安全的同时实现代码复用
- 让编译器自动推断具体类型,减少手动标注
- 约束输入输出之间的类型关系
一、泛型基础
泛型函数
typescript
// 没有泛型:丢失类型信息
function identity(arg: any): any {
return arg;
}
// 有泛型:保持类型安全
function identity<T>(arg: T): T {
return arg;
}
// 调用方式
identity<string>('hello'); // 显式指定
identity('hello'); // 自动推断(推荐)
identity(42); // T = number泛型接口
typescript
interface Repository<T> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
create(data: Omit<T, 'id'>): Promise<T>;
update(id: string, data: Partial<T>): Promise<T>;
delete(id: string): Promise<void>;
}
interface User {
id: string;
name: string;
email: string;
}
// 使用时指定具体类型
const userRepo: Repository<User> = {
async findById(id) { /* ... */ return null; },
async findAll() { /* ... */ return []; },
async create(data) { /* ... */ return { id: '1', ...data }; },
async update(id, data) { /* ... */ return {} as User; },
async delete(id) { /* ... */ },
};泛型类型别名
typescript
// 通用响应类型
type ApiResponse<T> = {
code: number;
data: T;
message: string;
timestamp: number;
};
// 分页响应
type PaginatedResponse<T> = ApiResponse<{
items: T[];
total: number;
page: number;
pageSize: number;
}>;
// 使用
type UserListResponse = PaginatedResponse<User>;
// { code: number; data: { items: User[]; total: number; ... }; ... }二、泛型约束
extends 约束
typescript
// 约束 T 必须有 length 属性
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest('hello', 'hi'); // ✅ string 有 length
longest([1, 2], [1, 2, 3]); // ✅ 数组有 length
// longest(10, 20); // ❌ number 没有 lengthkeyof 约束
typescript
// 约束 K 必须是 T 的键
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'Alice', age: 25, email: 'alice@example.com' };
getProperty(user, 'name'); // string
getProperty(user, 'age'); // number
// getProperty(user, 'foo'); // ❌ "foo" 不是 keyof User
// 安全的 pick 函数
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
keys.forEach(key => { result[key] = obj[key]; });
return result;
}
const partial = pick(user, ['name', 'email']);
// { name: string; email: string }多重约束
typescript
// 同时满足多个约束
interface Serializable {
serialize(): string;
}
interface Loggable {
log(): void;
}
function process<T extends Serializable & Loggable>(item: T): string {
item.log();
return item.serialize();
}约束中使用类型参数
typescript
// K 约束依赖于 T
function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
obj[key] = value;
}
const user = { name: 'Alice', age: 25 };
setProperty(user, 'name', 'Bob'); // ✅
// setProperty(user, 'name', 42); // ❌ number 不能赋给 string
// setProperty(user, 'age', 'old'); // ❌ string 不能赋给 number三、泛型默认值
基本语法
typescript
// 默认值让泛型参数可选
interface ApiResponse<T = unknown> {
code: number;
data: T;
message: string;
}
// 不指定时使用默认值
const res1: ApiResponse = { code: 200, data: 'anything', message: 'ok' };
// data: unknown
// 指定时覆盖默认值
const res2: ApiResponse<User> = { code: 200, data: user, message: 'ok' };
// data: User默认值 + 约束
typescript
// 默认值必须满足约束
interface Collection<T extends object = Record<string, unknown>> {
items: T[];
count: number;
}
const c1: Collection = { items: [{ a: 1 }], count: 1 };
// T = Record<string, unknown>
const c2: Collection<User> = { items: [user], count: 1 };
// T = User多个泛型参数的默认值顺序
typescript
// 有默认值的参数必须在没有默认值的参数后面
interface Mapper<Input, Output = Input> {
map(input: Input): Output;
}
const identity: Mapper<string> = {
map: (input) => input, // Output 默认为 string
};
const toNumber: Mapper<string, number> = {
map: (input) => parseInt(input),
};四、const 类型参数(TS 5.0+)
问题:泛型推断会拓宽类型
typescript
function createConfig<T extends Record<string, string>>(config: T): T {
return config;
}
const config = createConfig({
home: '/',
about: '/about',
});
// T = { home: string; about: string } ❌ 字面量信息丢失解决:<const T> 自动推断为字面量类型
typescript
function createConfig<const T extends Record<string, string>>(config: T): T {
return config;
}
const config = createConfig({
home: '/',
about: '/about',
});
// T = { readonly home: "/"; readonly about: "/about" } ✅ 保留字面量
// 等价于调用方写 as const,但对调用方更友好实际应用
typescript
// 1. 类型安全的路由定义
function defineRoutes<const T extends readonly { path: string; name: string }[]>(
routes: T
) {
return routes;
}
const routes = defineRoutes([
{ path: '/', name: 'home' },
{ path: '/about', name: 'about' },
]);
// routes[0].name 的类型是 "home" 而非 string
type RouteName = (typeof routes)[number]['name'];
// "home" | "about"
// 2. 事件系统
function defineEvents<const T extends Record<string, (...args: any[]) => void>>(
events: T
) {
return events;
}
const events = defineEvents({
click: (x: number, y: number) => {},
resize: (width: number, height: number) => {},
});
// 3. 状态机
function createMachine<const T extends {
initial: string;
states: Record<string, { on?: Record<string, string> }>;
}>(config: T) {
return config;
}
const machine = createMachine({
initial: 'idle',
states: {
idle: { on: { START: 'running' } },
running: { on: { STOP: 'idle' } },
},
});
// machine.initial 的类型是 "idle" 而非 string五、高阶泛型模式
泛型工厂函数
typescript
// 创建类型安全的工厂
function createFactory<T>(Constructor: new (...args: any[]) => T) {
return {
create(...args: ConstructorParameters<typeof Constructor>): T {
return new Constructor(...args);
},
};
}
class User {
constructor(public name: string, public age: number) {}
}
const userFactory = createFactory(User);
const user = userFactory.create('Alice', 25); // User 类型函数重载 vs 泛型
typescript
// ❌ 重载:每种组合都要写一遍
function parse(input: string): string;
function parse(input: number): number;
function parse(input: string | number) {
return input;
}
// ✅ 泛型:一次定义覆盖所有
function parse<T extends string | number>(input: T): T {
return input;
}Builder 模式(链式调用保持类型)
typescript
class QueryBuilder<T extends object, Selected extends keyof T = never> {
private selectedFields: (keyof T)[] = [];
private conditions: string[] = [];
select<K extends keyof T>(...fields: K[]): QueryBuilder<T, Selected | K> {
this.selectedFields.push(...fields);
return this as any;
}
where(condition: string): this {
this.conditions.push(condition);
return this;
}
execute(): Promise<Pick<T, Selected>[]> {
// 实际执行查询
return Promise.resolve([]);
}
}
interface User {
id: number;
name: string;
email: string;
age: number;
}
// 链式调用,类型逐步收窄
const result = await new QueryBuilder<User>()
.select('name', 'email')
.where('age > 18')
.execute();
// result: Pick<User, "name" | "email">[]
// 即 { name: string; email: string }[]泛型与 React
typescript
// 泛型组件
interface SelectProps<T> {
options: T[];
value: T;
onChange: (value: T) => void;
getLabel: (option: T) => string;
getValue: (option: T) => string | number;
}
function Select<T>({ options, value, onChange, getLabel, getValue }: SelectProps<T>) {
return (
<select
value={String(getValue(value))}
onChange={(e) => {
const selected = options.find(o => String(getValue(o)) === e.target.value);
if (selected) onChange(selected);
}}
>
{options.map(option => (
<option key={String(getValue(option))} value={String(getValue(option))}>
{getLabel(option)}
</option>
))}
</select>
);
}
// 使用时 T 自动推断
interface Country { code: string; name: string }
<Select<Country>
options={[{ code: 'CN', name: '中国' }, { code: 'US', name: '美国' }]}
value={{ code: 'CN', name: '中国' }}
onChange={(country) => console.log(country.name)} // country: Country
getLabel={(c) => c.name}
getValue={(c) => c.code}
/>
// 泛型 Hook
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
const setStoredValue = (newValue: T | ((prev: T) => T)) => {
setValue(prev => {
const resolved = typeof newValue === 'function'
? (newValue as (prev: T) => T)(prev)
: newValue;
localStorage.setItem(key, JSON.stringify(resolved));
return resolved;
});
};
return [value, setStoredValue] as const;
}
// T 从 initialValue 推断
const [theme, setTheme] = useLocalStorage('theme', 'dark');
// theme: string, setTheme: (value: string | ((prev: string) => string)) => void六、NoInfer<T>(TS 5.4+)
问题:多位置推断冲突
typescript
function createState<T>(initial: T, defaultValue: T) {
return { value: initial, default: defaultValue };
}
// T 从两个位置推断,结果可能不符合预期
createState('hello', 42);
// T 被推断为 string | number,不报错解决:阻止特定位置参与推断
typescript
function createState<T>(initial: T, defaultValue: NoInfer<T>) {
return { value: initial, default: defaultValue };
}
// T 只从 initial 推断为 string
createState('hello', 42);
// ❌ Argument of type 'number' is not assignable to parameter of type 'string'
createState('hello', 'world'); // ✅实际场景
typescript
// 1. 带默认值的配置
function configure<T extends object>(
schema: T,
defaults: NoInfer<Partial<T>>
) { /* ... */ }
// T 只从 schema 推断
configure(
{ host: '', port: 0, debug: false },
{ debug: true } // 受 schema 类型约束
);
// 2. 事件系统
function on<T extends string>(
event: T,
handler: (data: NoInfer<T>) => void
) { /* ... */ }
// T 只从 event 推断
on('click', (data) => {
// data: "click"
});面试高频题
1. 泛型和 any 的区别是什么?
答案:any 放弃类型检查,输入和输出之间没有类型关联。泛型保持类型安全,输入类型决定输出类型。例如 function first<T>(arr: T[]): T 保证返回值类型与数组元素类型一致,而 function first(arr: any[]): any 丢失了这个关联。
2. 什么时候用泛型约束?extends 在泛型中的含义?
答案:当需要限制泛型参数的范围时使用约束。T extends U 表示 T 必须是 U 的子类型(结构化兼容),不是面向对象的"继承"。常见模式:T extends object(必须是对象)、K extends keyof T(必须是 T 的键)、T extends { length: number }(必须有 length 属性)。
3. <const T> 和 as const 的区别?
答案:as const 是调用方在值后面加的断言,<const T> 是库开发者在泛型定义上加的修饰。效果相同(推断为字面量类型),但 <const T> 对调用方更友好——调用方不需要记住加 as const,类型定义方就能保证推断精度。
4. 泛型在 React 中有哪些典型应用?
答案:泛型列表组件(List<T>)、泛型选择器(Select<T>)、泛型表单(Form<T>)、泛型 Hook(useLocalStorage<T>、useFetch<T>)。TS 5.x 支持泛型 forwardRef,解决了之前泛型组件无法转发 ref 的问题。