TypeScript 泛型完全指南
泛型是 TypeScript 中最强大的特性之一,它让我们可以编写灵活且类型安全的可复用代码。
一、为什么需要泛型
没有泛型的问题
// 方案1:用 any — 丢失类型信息
function identity(value: any): any {
return value
}
const result = identity('hello') // result 是 any,没有类型提示
// 方案2:为每种类型写一个函数 — 太多重复代码
function identityString(value: string): string { return value }
function identityNumber(value: number): number { return value }
泛型解决方案
function identity<T>(value: T): T {
return value
}
const str = identity('hello') // T 推断为 string,str 是 string
const num = identity(42) // T 推断为 number,num 是 number
const explicit = identity<boolean>(true) // 显式指定 T 为 boolean
二、泛型基础
函数泛型
// 单个类型参数
function first<T>(arr: T[]): T {
return arr[0]
}
// 多个类型参数
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value]
}
const p = pair('name', 'Alice') // [string, string]
const p2 = pair(1, true) // [number, boolean]
接口泛型
interface Box<T> {
value: T
}
const strBox: Box<string> = { value: 'hello' }
const numBox: Box<number> = { value: 42 }
类泛型
class Stack<T> {
private items: T[] = []
push(item: T): void {
this.items.push(item)
}
pop(): T | undefined {
return this.items.pop()
}
peek(): T | undefined {
return this.items[this.items.length - 1]
}
}
const numStack = new Stack<number>()
numStack.push(1)
numStack.push(2)
console.log(numStack.pop()) // 2
const strStack = new Stack<string>()
strStack.push('hello')
三、类型约束
使用 extends 约束
// 约束 T 必须有 length 属性
function getLength<T extends { length: number }>(item: T): number {
return item.length
}
getLength('hello') // 5
getLength([1, 2, 3]) // 3
getLength({ length: 10 }) // 10
// getLength(123) // ❌ 错误:number 没有 length
使用 keyof
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const person = { name: 'Alice', age: 30, city: 'Beijing' }
getProperty(person, 'name') // string
getProperty(person, 'age') // number
// getProperty(person, 'email') // ❌ 错误:'email' 不是 person 的属性
多重约束
interface HasId {
id: number
}
interface HasName {
name: string
}
function display<T extends HasId & HasName>(item: T): string {
return `${item.id}: ${item.name}`
}
四、实用泛型模式
1. API 响应封装
interface ApiResponse<T> {
code: number
message: string
data: T
}
interface User {
id: number
name: string
}
interface Product {
id: number
price: number
}
// 不同接口返回不同类型的数据
const userRes: ApiResponse<User> = {
code: 200,
message: 'success',
data: { id: 1, name: 'Alice' }
}
const productRes: ApiResponse<Product> = {
code: 200,
message: 'success',
data: { id: 1, price: 99.9 }
}
2. 工厂函数
function create<T>(type: { new(): T }): T {
return new type()
}
class Animal {
name = 'Animal'
}
const animal = create(Animal) // Animal
3. Partial 和 Required
interface User {
id: number
name: string
email: string
}
// Partial:所有属性变为可选(更新时很有用)
type UpdateUser = Partial<User>
// 等价于 { id?: number; name?: string; email?: string }
// 更新用户信息
function updateUser(id: number, data: UpdateUser): void {
// 只更新传入的字段
}
updateUser(1, { name: 'New Name' }) // ✅ 只更新 name
4. Pick 和 Omit
interface User {
id: number
name: string
email: string
password: string
}
// Pick:选取部分属性
type UserSummary = Pick<User, 'id' | 'name'>
// { id: number; name: string }
// Omit:排除部分属性
type SafeUser = Omit<User, 'password'>
// { id: number; name: string; email: string }
5. Record
// Record<K, V>:键类型为 K,值类型为 V 的对象
type UserMap = Record<string, User>
const users: UserMap = {
'alice': { id: 1, name: 'Alice', email: 'a@b.com', password: '123' },
'bob': { id: 2, name: 'Bob', email: 'b@b.com', password: '456' },
}
// 配合字面量联合类型
type EventType = 'click' | 'hover' | 'focus'
type EventHandler = Record<EventType, Function>
const handlers: EventHandler = {
click: () => {},
hover: () => {},
focus: () => {},
}
6. 条件类型
// 如果 T 是数组,返回数组元素类型,否则返回 T
type Unpacked<T> = T extends (infer U)[] ? U : T
type A = Unpacked<string[]> // string
type B = Unpacked<number> // number
type C = Unpacked<boolean[]> // boolean
五、泛型在 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>
)
}
// 使用
const users = [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
]
<List
items={users}
renderItem={(user) => <span>{user.name}</span>}
keyExtractor={(user) => user.id}
/>
自定义 Hook 泛型
function useLocalStorage<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key)
return stored ? JSON.parse(stored) : initial
})
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value))
}, [key, value])
return [value, setValue] as const
}
// 使用
const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light')
const [count, setCount] = useLocalStorage<number>('count', 0)
六、泛型工具类型
TypeScript 内置了很多实用的工具类型:
// Partial<T> — 所有属性可选
// Required<T> — 所有属性必填
// Readonly<T> — 所有属性只读
// Pick<T, K> — 选取部分属性
// Omit<T, K> — 排除部分属性
// Record<K, V> — 构造键值对类型
// ReturnType<F> — 获取函数返回类型
// Parameters<F> — 获取函数参数类型
// Awaited<P> — 获取 Promise 解析类型
// Exclude<T, U> — 从联合类型中排除
// Extract<T, U> — 从联合类型中提取
// NonNullable<T> — 排除 null 和 undefined
总结
泛型是 TypeScript 的核心特性:
- 灵活性 — 一套代码适配多种类型
- 类型安全 — 编译时捕获类型错误
- 代码复用 — 避免重复编写相似逻辑
- 开发体验 — 完善的类型提示和自动补全
从今天开始,用泛型让你的 TypeScript 代码更上一层楼!📘
📖 相关阅读
⚡
前端开发
2024-10-20
Web 性能优化实战:从首屏加载到运行时流畅
系统性地讲解 Web 性能优化的各个方面,包括加载性能、渲染性能、运行时性能的优化策略。
#性能优化#前端#Web
14 分钟阅读
阅读全文 →
🔗
前端开发
2024-12-15
React 开发最佳实践指南(2024 版)
从组件设计到状态管理,从性能优化到代码规范,全面总结 React 开发中的最佳实践。
#React#前端#最佳实践
12 分钟阅读
阅读全文 →
⚙️
前端开发
2024-11-10
深入理解 JavaScript 异步编程
从回调到 Promise 再到 async/await,彻底搞懂 JavaScript 异步编程的演进与最佳实践。
#JavaScript#异步编程#前端
15 分钟阅读
阅读全文 →