前端开发

TypeScript 泛型完全指南

从基础到高级,彻底搞懂 TypeScript 泛型,写出更灵活、更类型安全的代码。

站长 12 分钟阅读

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 代码更上一层楼!📘