泛型解决的核心问题:逻辑相同,但数据类型不同。


泛型函数

// 没有泛型:每个类型都要写一遍
function getFirstString(arr: string[]): string | undefined {
  return arr[0]
}

function getFirstNumber(arr: number[]): number | undefined {
  return arr[0]
}

// 有泛型:写一次,适用所有类型
function getFirst<T>(arr: T[]): T | undefined {
  return arr[0]
}

getFirst(['a', 'b', 'c']) // 类型推导为 string | undefined
getFirst([1, 2, 3]) // 类型推导为 number | undefined

T 是类型参数,调用时由 TypeScript 自动推导。


类型参数如何贯穿输入与输出

function identity<T>(value: T): T {
  return value
}

const result = identity('hello') // 类型推导为 string

类型信息从输入(参数)贯穿到输出(返回值)。

更复杂的例子:

function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
  return arr.map(fn)
}

const numbers = map(['1', '2', '3'], Number) // 类型推导为 number[]
  • 输入:T = stringU = number
  • 输出:U[] = number[]

类型参数让函数的类型关系变得显式。


泛型约束

// 没有约束:T 可以是任何类型
function getProperty<T>(obj: T, key: string) {
  return obj[key] // ❌ 编译错误:T 上不存在属性 key
}

// 有约束:T 必须有 key 属性
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' 不在 'keyof { name: string; age: number }' 中

keyof T 获取 T 的所有属性名组成的联合类型。K extends keyof T 约束 K 必须是 T 的属性名之一。


泛型组件

interface SelectProps<T extends string> {
  options: { value: T; label: string }[]
  value: T
  onChange: (value: T) => void
}

function Select<T extends string>({ options, value, onChange }: SelectProps<T>) {
  return (
    <select value={value} onChange={(e) => onChange(e.target.value as T)}>
      {options.map((opt) => (
        <option key={opt.value} value={opt.value}>{opt.label}</option>
      ))}
    </select>
  )
}

使用时:

type Fruit = 'apple' | 'banana' | 'orange'

const options: { value: Fruit; label: string }[] = [
  { value: 'apple', label: 'Apple' },
  { value: 'banana', label: 'Banana' },
  { value: 'orange', label: 'Orange' },
]

function App() {
  const [fruit, setFruit] = useState<Fruit>('apple')

  return (
    <Select
      options={options}
      value={fruit}
      onChange={(value) => {
        // value 的类型是 Fruit,不是 string
        setFruit(value)
      }}
    />
  )
}

onChange 的参数类型是 Fruit,不是 string。类型安全从输入贯穿到输出。


核心洞察

泛型解决的不是"高级语法问题",而是"让组件的类型参数由使用者决定,而不是写死在组件里"。

输入类型

泛型参数

内部逻辑

输出类型

类型信息贯穿整个 API。