Skip to main content

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 类型体操的基石,几乎所有工具类型(PickOmitPartial)都基于这个模式。


三个值得记住的内置工具

工具作用等价手写
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

  1. TypeScript Handbook: Generics —— 官方手册
  2. TypeScript Handbook: Generic Constraints —— 约束语法详解