泛型解决的核心问题:逻辑相同,但数据类型不同。
泛型函数
// 没有泛型:每个类型都要写一遍
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 = string,U = 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。