TypeScript 泛型函数怎么用?
· 3 min read
泛型函数是 TS 类型系统里复用率最高的特性——核心是 类型变量 T + 约束 extends 两个机制。
- 基础用法:
function id<T>(arg: T): T——比any安全 - 类型推断:调用时不用显式
<T>,TS 自动推断 - 多类型变量:
<T, U>分别约束不同位置 - 约束语法:
T extends Person限定类型范围 - 避坑:泛型里访问属性前必须 narrow 或约束
为什么不用 any
普通函数返回 any 丢失了类型信息:
function id(arg: any): any {
return arg
}
const x = id('hello')
x.toUpperCase() // 编译通过,运行炸(如果传入的不是 string)
泛型保留入参和返回的类型关系:
function id<T>(arg: T): T {
return arg
}
const x = id('hello')
x.toUpperCase() // OK,x 推断为 string
类型推断:让 TS 自己猜
function id<T>(arg: T): T {
return arg
}
const a = id<string>('foo') // 显式指定
const b = id('foo') // TS 自动推断 T = string
tip
优先用类型推断,少写 <T>——代码更简洁,可读性更好。
多类型变量
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second]
}
pair(1, 'one') // [number, string]
pair<number, string>(1, 'one') // 等价
只有部分参数用类型变量也行:
function labeled<T>(value: T, label: string): { value: T; label: string } {
return { value, label }
}
约束:限制 T 的范围
默认 T 可以是任何类型,访问特定属性会报错:
function len<T>(arg: T): number {
return arg.length // Error: T doesn't have .length
}
用 extends 约束:
interface HasLength {
length: number
}
function len<T extends HasLength>(arg: T): number {
return arg.length // OK
}
len('hello') // OK,string 有 length
len([1, 2, 3]) // OK,array 有 length
len(123) // Error,number 没有 length
进阶约束:keyof + extends
最常见的模式——限定 key 必须是对象的属性:
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = { name: 'Kimi', age: 20 }
get(user, 'name') // string
get(user, 'age') // number
get(user, 'foo') // Error: 'foo' 不是 user 的 key
这是 TS 类型体操的基石,几乎所有工具类型(Pick、Omit、Partial)都基于这个模式。
三个值得记住的内置工具
| 工具 | 作用 | 等价手写 |
|---|---|---|
Partial<T> | 所有字段可选 | { [K in keyof T]?: T[K] } |
Pick<T, K> | 挑选字段 | { [P in K]: T[P] } |
Readonly<T> | 所有字段只读 | { readonly [K in keyof T]: T[K] } |
warning
工具类型是泛型约束的应用典范,但手写它们容易出错。新项目直接用内置,老项目迁移时再手写替换。
References
- TypeScript Handbook: Generics —— 官方手册
- TypeScript Handbook: Generic Constraints —— 约束语法详解