TypeScript 03:泛型与工具类型
有 JS 基础即可阅读。建议先读第 01、02 课。
1. 先看问题:逻辑一样,类型不同
ts
function wrapStr(v: string): string[] {
return [v]
}
function wrapNum(v: number): number[] {
return [v]
}
// 逻辑一字不差,只有「类型」不同 → 只能复制粘贴
想复用,通常的歪路是 any------能复用,但检查全丢:
ts
function wrapAny(v: any): any[] {
return [v]
}
const n = wrapAny(42)
n.push('oops') // ✅ 编译通过,运行时数组混进了字符串
2. 泛型:把「类型」也变成参数
函数参数传的是值 ,泛型传的是类型 。T 是类型的占位符,调用时才被填上:
ts
// 类型的参数 值的参数
function wrap<T>(v: T): T[] {
return [v]
}
wrap('hi') // ① 'hi' 是 string → ② 函数里所有 T 替换成 string
wrap(42) // T 替换成 number
心智模型一句话:
函数只有一份,类型是每次调用时「现印」的------把 TS 想成复印机。
wrap('hi')印出string[]版,wrap(42)印出number[]版。
所以泛型 = 复用 + 精确两全:逻辑写一遍,每次调用类型各自精确。
3. 关键澄清:T 只在「定义处」存在
初学最常见的误解是以为返回值「还是泛型」------不,每个返回值拿到的是印好的具体版本:
ts
const a = wrap('hi') // a 是 string[],T 已经变成了 string
a.push(1) // ❌ Argument of type 'number' is not assignable to parameter of type 'string'
4. 你早就在用泛型,只是没人说破
ts
const x: string[] = []
const y: Array<string> = [] // 完全等价![] 是简写,尖括号里就是那个 T
Array<string> 读作「一个数组,元素类型用的时候才给」。Vue 的 Ref<number>、各种库源码里的 <T>,全是同一台复印机。
5. 泛型接口
ts
interface Box<T> {
value: T
}
const b1: Box<number> = { value: 42 }
const b2: Box<string> = { value: 'abc' }
6. 约束 extends:给 T 设「准入条件」
泛型把「传什么类型」的权利交给了调用方------对方可能传任何东西进来。想在函数体里用 x.length,就得提前提条件:
ts
function getLength<T extends { length: number }>(x: T): number {
return x.length // 没有这行 extends,TS 不敢保证 T 有 length
}
getLength('hello') // ✅ string 有 length
getLength([1, 2, 3]) // ✅
getLength(123) // ❌ Type 'number' does not satisfy the constraint '{ length: number }'
7. 进阶:keyof 与工具类型(初学可先跳过,用到再回来看)
keyof:拿到「字段名的联合类型」;T['字段']:从类型上取出字段的类型。
ts
interface User {
name: string
age: number
}
type UserKeys = keyof User // 'name' | 'age'
type AgeType = User['age'] // number
工具类型 = 内置的「类型函数」:接收一个类型,加工后返回新类型。
ts
interface User {
name: string
age: number
email: string
}
Partial<User> // 全部变可选 → 典型场景:局部更新(patch 只传要改的字段)
Required<User> // 全部变必填
Pick<User, 'name' | 'age'> // 只挑这几个字段 → 接口瘦身
Omit<User, 'email'> // 排除字段
Record<'host' | 'port', number> // { host: number; port: number } → 字典 / 配置
面纱:工具类型没有魔法,就是「泛型 + 遍历字段」:
ts
type MyPartial<T> = {
[K in keyof T]?: T[K] // 遍历 T 的每个字段 K,把类型 T[K] 变为可选
}
从函数提取类型(类型世界的 typeof 拿「类型形状」,和运行时 typeof 同名不同物):
ts
function createUser() {
return { name: 'Tom', age: 18 }
}
type NewUser = ReturnType<typeof createUser> // { name: string; age: number }
8. 高频报错速查
| 报错 | 人话 |
|---|---|
Argument of type 'number' is not assignable to parameter of type 'string' |
泛型 T 已推断锁定,不能再塞别的类型 |
Type 'number' does not satisfy the constraint '{ length: number }' |
T 不满足 extends 约束 |
Type '"email"' is not assignable to type 'keyof User' |
取了不存在的字段名 |
Property 'port' is missing in type ... |
Record 声明的键必须一个不少 |