TypeScript 泛型
·
前言
泛型是 TypeScript 类型系统的核心特性,让代码在保持类型安全的同时具备复用性。本篇会讲清楚:
- 泛型基础用法
- 泛型约束
infer关键字- 常用工具类型的实现原理
一、泛型基础
1.1 为什么需要泛型
// 问题:any 丢失类型信息
function identity(value: any): any {
return value
}
const result = identity('hello') // result 类型是 any
// 泛型:保留类型信息
function identity<T>(value: T): T {
return value
}
const result2 = identity('hello') // result2 类型是 string
1.2 泛型函数
// 单类型参数
function first<T>(arr: T[]): T | undefined {
return arr[0]
}
first([1, 2, 3]) // number
first(['a', 'b']) // string
// 多类型参数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second]
}
pair('hello', 42) // [string, number]
1.3 泛型接口
interface ApiResponse<T> {
code: number
message: string
data: T
}
interface User {
id: number
name: string
}
// 使用
const response: ApiResponse<User> = {
code: 200,
message: 'success',
data: { id: 1, name: 'Alice' }
}
1.4 泛型类
class Stack<T> {
private items: T[] = []
push(item: T): void {
this.items.push(item)
}
pop(): T | undefined {
return this.items.pop()
}
}
const numberStack = new Stack<number>()
numberStack.push(1)
numberStack.push(2)
// numberStack.push('3') // 错误:不能将 string 赋值给 number
二、泛型约束
2.1 extends 约束
// 约束 T 必须有 length 属性
interface HasLength {
length: number
}
function getLength<T extends HasLength>(value: T): number {
return value.length
}
getLength('hello') // ✅ string 有 length
getLength([1, 2, 3]) // ✅ array 有 length
// getLength(123) // ❌ number 没有 length
2.2 keyof 约束
// 约束 K 必须是 T 的键
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = { name: 'Alice', age: 25 }
getProperty(user, 'name') // ✅ string
getProperty(user, 'age') // ✅ number
// getProperty(user, 'email') // ❌ 'email' 不是 user 的键
2.3 多重约束
interface Printable {
print(): void
}
interface Loggable {
log(): void
}
// T 必须同时实现 Printable 和 Loggable
function process<T extends Printable & Loggable>(item: T): void {
item.print()
item.log()
}
三、infer 关键字
3.1 基本用法
infer 用于在条件类型中推断类型。
// 推断数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : T
type A = ElementType<number[]> // number
type B = ElementType<string[]> // string
type C = ElementType<boolean> // boolean
3.2 推断函数返回值
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type A = ReturnType<() => string> // string
type B = ReturnType<(x: number) => boolean> // boolean
3.3 推断函数参数
type Parameters<T> = T extends (...args: infer P) => any ? P : never
type A = Parameters<(a: string, b: number) => void> // [string, number]
3.4 推断 Promise 内部类型
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T
type A = Awaited<Promise<string>> // string
type B = Awaited<Promise<Promise<number>>> // number
四、常用工具类型实现
4.1 Partial
// 将所有属性变为可选
type Partial<T> = {
[P in keyof T]?: T[P]
}
interface User {
id: number
name: string
}
type PartialUser = Partial<User>
// { id?: number; name?: string }
4.2 Required
// 将所有属性变为必选
type Required<T> = {
[P in keyof T]-?: T[P]
}
interface User {
id?: number
name?: string
}
type RequiredUser = Required<User>
// { id: number; name: string }
4.3 Readonly
// 将所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
type ReadonlyUser = Readonly<User>
// { readonly id: number; readonly name: string }
4.4 Pick
// 从 T 中选取指定属性 K
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
type UserName = Pick<User, 'name'>
// { name: string }
4.5 Omit
// 从 T 中排除指定属性 K
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
type UserWithoutId = Omit<User, 'id'>
// { name: string }
4.6 Record
// 构造一个属性键为 K,属性值为 T 的类型
type Record<K extends keyof any, T> = {
[P in K]: T
}
type UserRoles = Record<string, string[]>
// { [key: string]: string[] }
4.7 Exclude / Extract
// Exclude:从 T 中排除可赋值给 U 的类型
type Exclude<T, U> = T extends U ? never : T
type A = Exclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
// Extract:从 T 中提取可赋值给 U 的类型
type Extract<T, U> = T extends U ? T : never
type B = Extract<'a' | 'b' | 'c', 'a' | 'c'> // 'a' | 'c'
五、泛型实战
5.1 泛型工厂函数
function createInstance<T>(Ctor: new () => T): T {
return new Ctor()
}
class User {
name = 'Alice'
}
const user = createInstance(User) // User 类型
5.2 泛型约束 API 响应
interface ApiRequest<T> {
url: string
method: 'GET' | 'POST'
data?: T
}
interface ApiResponse<T> {
code: number
data: T
}
async function request<T, R>(config: ApiRequest<T>): Promise<ApiResponse<R>> {
const response = await fetch(config.url)
return response.json()
}
// 使用
const users = await request<null, User[]>({
url: '/api/users',
method: 'GET'
})
5.3 泛型与 React 组件
// 泛型列表组件
interface ListProps<T> {
items: T[]
renderItem: (item: T) => React.ReactNode
keyExtractor: (item: T) => string
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
)
}
// 使用
<List
items={users}
renderItem={user => <span>{user.name}</span>}
keyExtractor={user => user.id.toString()}
/>
六、面试聚焦
6.1 泛型 vs any
// any:丢失类型信息,运行时可能出错
function process(value: any): any {
return value.toUpperCase() // 运行时才知道是否有这个方法
}
// 泛型:保留类型信息,编译时检查
function process<T>(value: T): T {
// return value.toUpperCase() // ❌ 编译错误:T 没有 toUpperCase
return value
}
// 泛型约束:限制类型范围
function process<T extends string>(value: T): T {
return value.toUpperCase() // ✅ T 被约束为 string
}
6.2 ReturnType 原理一句话
// ReturnType 的实现原理:
// 使用 infer 推断函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
// 解读:如果 T 是函数类型,推断返回值为 R,否则为 never
七、易混淆点
- 泛型 vs any:
any放弃类型检查,泛型保留类型信息,在使用时确定具体类型。 - infer 的位置:
infer只能在条件类型的extends子句中使用,用于推断类型变量。 - 泛型约束:
extends不是继承,是约束泛型参数必须满足某个条件。 - 工具类型是类型:
Partial<T>、Pick<T, K>等是类型运算,不是运行时操作。
八、思考与练习
1. 泛型和 any 的区别是什么?
解析:
- any:放弃类型检查,运行时可能出错
- 泛型:保留类型信息,编译时检查,使用时确定具体类型
2. infer 的作用是什么?只能在哪里使用?
解析:
infer用于在条件类型中推断类型变量- 只能在
extends子句中使用
3. 实现一个 DeepPartial 类型。
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}
4. Pick 和 Omit 的区别是什么?
解析:
Pick<T, K>:从 T 中选取指定属性 KOmit<T, K>:从 T 中排除指定属性 K
type A = Pick<User, 'name'> // { name: string }
type B = Omit<User, 'name'> // { id: number }
5. 解释 Partial<T> 的实现原理。
解析:
type Partial<T> = {
[P in keyof T]?: T[P]
}
// 解读:
// 1. keyof T:获取 T 的所有属性键
// 2. P in keyof T:遍历所有属性键
// 3. ?:将每个属性变为可选
// 4. T[P]:保持原属性类型
总结
- 泛型让代码在保持类型安全的同时具备复用性
- 泛型约束通过
extends限制泛型参数的范围 - infer 用于在条件类型中推断类型变量
- 工具类型是类型运算,编译时完成,不影响运行时
- 泛型 vs any:泛型保留类型信息,any 放弃类型检查
更多推荐


所有评论(0)