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 声明的键必须一个不少
相关推荐
Csvn9 小时前
WASM
前端
修己xj10 小时前
我用 AI 给自己写了一个 Markdown 编辑器,然后把它开源了
前端
FRing0310 小时前
vue2干货总结2(v-bind、v-for、v-model、指令修饰符、v-bind操作class和style)
开发语言·前端·vue.js·vue2
幸运小猿11 小时前
模拟慢上传-处理超时重试image 丢失线上问题
前端·javascript·html
moshuying12 小时前
网购能买Token了!
前端
h_654321012 小时前
echarts实现柱状图加一条横线
前端·echarts
风骏时光牛马13 小时前
生成一个后端标题,只需要一个标题,不需要其他的任何文本。
前端
子非鱼@Itfuture13 小时前
关于前后端联调中登录状态/登录信息失效?
java·前端
郭小李13 小时前
openFeign入门
java·开发语言·前端
馒头不想说话13 小时前
AI当运营,帮你省人力
前端