Skip to content
泛型类与内置工具类型
概述
泛型类在类名后使用 <T> 声明类型参数,实例化时由调用方传入具体类型。与泛型函数类似,类型参数在类体内部的属性、构造函数和方法之间保持一致。TypeScript 还内置了一组泛型工具类型,定义在 lib.es5.d.ts 中,它们只在类型层面做变换——输入一个类型,输出一个新类型,编译后不会产生任何 JavaScript 代码。
泛型类
声明与实例化
typescript
class Box<T> {
value: T;
constructor(value: T) {
this.value = value;
}
}
const box = new Box<string>('hello');
// box.value 的类型为 string实例化时如果省略类型参数,TypeScript 会从构造函数实参自动推断:
typescript
const box = new Box('hello');
// 类型推断为 Box<string>类型参数在属性与方法上的传递
类声明的类型参数可以贯穿实例属性、构造函数参数、方法参数和返回值:
typescript
class Pair<K, V> {
key: K;
value: V;
constructor(key: K, value: V) {
this.key = key;
this.value = value;
}
getKey(): K {
return this.key;
}
setValue(value: V): void {
this.value = value;
}
}方法本身也可以声明额外的类型参数,与类的类型参数独立:
typescript
class Box<T> {
value: T;
constructor(value: T) {
this.value = value;
}
map<U>(fn: (value: T) => U): U {
return fn(this.value);
}
}
const box = new Box(42);
const result = box.map(v => v.toString());
// result 的类型为 stringmap<U> 引入了自己的类型参数 U,从传入的 fn 推断。类级别的 T 与方法的 U 互不干扰,但可以通过函数签名建立关系——这里 fn 的输入是 T,输出是 U。
与普通类、接口的对比
普通类每个属性的类型在编写时已确定,一种类定义只服务一种数据形态。泛型类把类型的决定推迟到实例化时,同一个类定义可以处理多种类型。
typescript
// 普通类:只能存 number
class NumberBox {
value: number;
constructor(v: number) { this.value = v; }
}
// 泛型类:实例化时决定类型
class Box<T> {
value: T;
constructor(v: T) { this.value = v; }
}泛型接口只描述结构,不包含实现;泛型类同时提供实现,并且可以用 new 实例化。泛型类本身也充当类型,可用于变量标注:
typescript
class Container<T> {
item: T;
constructor(item: T) { this.item = item; }
}
let c: Container<number> = new Container(100);内置工具类型
TypeScript 内置了一组泛型工具类型,它们都在类型层面做变换。这里展开 Partial<T>、Record<K, V> 和 ReturnType<T>,其余的(Required、Pick、Omit、Exclude、Extract 等)会在后续关于条件类型和映射类型的部分涉及。
Partial<T>:属性全部可选
源码实现:
typescript
type Partial<T> = {
[P in keyof T]?: T[P];
};它遍历 T 的所有属性键,给每个属性加上 ?,得到一个所有属性都可选的版本。
typescript
interface AppConfig {
host: string;
port: number;
debug: boolean;
}
// AppConfig 的每个属性都变成可选
type PartialConfig = Partial<AppConfig>;
// { host?: string; port?: number; debug?: boolean; }配置对象合并
一个直接的应用场景是合并配置:基础配置提供所有字段的默认值,而用户只需要传入要覆盖的部分。Partial 正好描述“部分属性”这种形态。
typescript
function mergeConfig(
base: AppConfig,
patch: Partial<AppConfig>
): AppConfig {
return { ...base, ...patch };
}
const config = mergeConfig(
{ host: 'localhost', port: 3000, debug: false },
{ debug: true }
);
// { host: 'localhost', port: 3000, debug: true }调用方不用传完整的 AppConfig,只传要覆盖的字段即可。如果直接要求 patch 的类型为 AppConfig,那每次调用都得把三个字段都写上。
浅层处理
Partial<T> 只处理第一层属性。如果某个属性本身是对象类型,它的内部属性不会被递归地变为可选:
typescript
interface Nested {
outer: {
inner: number;
};
}
type PartialNested = Partial<Nested>;
// { outer?: { inner: number } }
// outer 是可选的,但 outer.inner 仍然是必填这是有意为之——递归地让深层属性可选在实际使用中经常导致意外的类型宽松,而且没有统一的“可选深度”约定。如果确实需要深层的 Partial,需要自己声明。
Record<K, V>:构建键值映射
源码实现:
typescript
type Record<K extends keyof any, T> = {
[P in K]: T;
};K 被约束为 keyof any,即 string | number | symbol。Record 把 K 中每个键映射到值类型 T,生成一个对象类型。
typescript
type PageInfo = Record<'title' | 'url', string>;
// { title: string; url: string }用联合类型指定有限的键集合,常用于枚举式映射。Record<string, T> 则支持任意字符串键,等价于 { [key: string]: T },但写法更短。
API 响应缓存
Node.js 中一个常见的缓存场景:用字符串键存取 API 返回的数据结构。
typescript
interface User {
id: number;
name: string;
}
class ResponseCache<T> {
private store: Record<string, T> = {};
set(key: string, value: T): void {
this.store[key] = value;
}
get(key: string): T | undefined {
return this.store[key];
}
}
const userCache = new ResponseCache<User>();
userCache.set('user:1', { id: 1, name: 'Alice' });
const user = userCache.get('user:1');
// user 的类型为 User | undefinedRecord<string, T> 保证存储的值类型统一为 T,而 get 返回 T | undefined 是因为运行时键可能不存在——类型系统把这一点反映出来了。
如果需要限定键的范围,可以用字面量联合:
typescript
type CacheKey = 'user:1' | 'user:2' | 'user:3';
type UserCache = Record<CacheKey, User>;
const cache: UserCache = {
'user:1': { id: 1, name: 'Alice' },
'user:2': { id: 2, name: 'Bob' },
'user:3': { id: 3, name: 'Charlie' },
};ReturnType<T>:提取函数返回值类型
源码实现:
typescript
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;ReturnType<T> 接收一个函数类型 T,用条件类型和 infer 推断其返回值类型 R。如果 T 不是函数类型,结果就是 any(类型参数约束 T extends (...args: any) => any 会在调用时先拦截非函数类型)。
typescript
type T0 = ReturnType<() => string>;
// string
type T1 = ReturnType<(s: string) => void>;
// void
function createUser() {
return { id: 1, name: 'Alice' };
}
type T2 = ReturnType<typeof createUser>;
// { id: number; name: string }注意这里用的是 typeof createUser——ReturnType 要的是函数类型,不是函数值。typeof 把运行时的函数值提升为它的类型。
中间件返回类型提取
Express/Connect 风格的中间件通常由一个工厂函数创建,工厂函数的返回值才是真正的中间件函数。手写中间件类型意味着重复声明 (req, res, next) 的参数类型,而且一旦签名变化就得改多处。
直接用 ReturnType<typeof factoryFunc> 从工厂函数推导:
typescript
import { Request, Response, NextFunction } from 'express';
function createAuthMiddleware() {
return (req: Request, res: Response, next: NextFunction) => {
// 验证逻辑
next();
};
}
type AuthMiddleware = ReturnType<typeof createAuthMiddleware>;
// AuthMiddleware = (req: Request, res: Response, next: NextFunction) => void工厂函数修改返回逻辑后,AuthMiddleware 自动跟进,不需要手动同步类型标注。
泛型类与工具类型的协作
泛型类和工具类型可以放在同一个类中使用,各自解决不同层面的问题:泛型类保持实例内部类型的一致性,工具类型在方法签名上做类型变换。
typescript
class Repository<T extends { id: string }> {
private items: Record<string, T> = {};
create(item: T): void {
this.items[item.id] = item;
}
update(id: string, patch: Partial<T>): void {
const current = this.items[id];
if (current) {
this.items[id] = { ...current, ...patch };
}
}
get(id: string): T | undefined {
return this.items[id];
}
}Record<string, T>用来存储完整对象,键为字符串id。Partial<T>描述更新操作时只传部分字段。- 泛型参数
T extends { id: string }保证每个存储项都有id属性,这是Record<string, T>和items[id]能正常工作的前提。 Repository<T>把T从存储到查询再到更新的整个生命周期贯串起来。
注意点
Partial<T>只做浅层可选。嵌套对象的深层属性不会被自动变为可选。Record<K, V>的K只能取string | number | symbol的子类型。用其他类型会编译报错。ReturnType<T>接收的是函数类型而不是函数值。如果手头有一个函数值,用typeof先取得类型:typescriptfunction fn() { return 42; } type R = ReturnType<typeof fn>; // 正确 type E = ReturnType<fn>; // 错误所有内置工具类型都只在编译期生效。它们在生成的 JavaScript 中不存在,也不能在运行时通过
typeof或instanceof检测。
参考链接
- [1] https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-classes
- [4] https://www.typescriptlang.org/docs/handbook/utility-types.html
- [5] https://github.com/chenxiaochun/blog/issues/67
- [6] https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype
- [8] https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type
- [9] https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype
- [13] [官方] 参考链接:TypeScript 泛型
