TypeScript 03:泛型与工具类型

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 声明的键必须一个不少
相关推荐
code_Bo1 小时前
从策划到上线:我和 AI 一起做了个微信记账小程序
前端·ai编程·cursor
Shirley~~1 小时前
Three.js加载Gltf模型并播放动画
前端·3d
Data analyse4561 小时前
Session超时与浏览器休眠的计时校验
大数据·前端·数据库·数据分析
星始流年2 小时前
AI 时代,软件程序员未来 10—15 年的职业前景
前端·后端·ai编程
猪猪拆迁队2 小时前
跨电脑复制共享-局域网直连踩坑
前端·后端·go
静默回滚2 小时前
逐帧处理视频的内存去哪了,我从看错读数查到没排队
前端
重庆穿山甲2 小时前
我们不做「看起来很聪明」的知识库:一套企业级 RAG 平台的 37 屏真机实录
前端
风花一世月2 小时前
🛩️ 我把一段夕阳飞行做成了能按键驾驶的网页(24 秒四段航线,在线直接玩)
前端
用户11481867894842 小时前
实现一个「配置驱动」的动态表格组件(Vue + Element UI)
前端