TypeScript 常用泛型工具函数
目录
什么是泛型工具类型?
1. 属性修饰符:让类型“变个身”
2. 结构过滤:像筛子一样筛选类型
3. 函数类型侦探:提取函数的秘密
4. 映射与构造:快速创建结构
5. 进阶实战:手写自定义工具
实战 1:DeepPartial (深度可选)
实战 2:Get (点符号访问类型)
参考官方文档:
什么是泛型工具类型?
简单来说,泛型工具类型就是用来操作类型的函数。
普通函数是Input -> Output(如map把数组转换成新数组)。
泛型工具类型是Type -> Type(如Partial把所有属性变成可选)。
TypeScript 内置了大量开箱即用的工具类型,覆盖了前端开发 90% 的场景。
1. 属性修饰符:让类型“变个身”
这是最基础也是最高频的一类工具,主要用于修改对象属性的读写性或必选性。
Partial<T>—— 变“全员可选”
场景:当你有一个“编辑用户信息”的表单,用户可能只想修改昵称,不想改头像。你不需要把所有字段都重写一遍。
interface User { id: number; name: string; age: number; avatar: string; } // 场景:定义更新接口,不需要传 id,且其他字段都可选 type UserUpdateForm = Partial<Omit<User, 'id'>>; // 等价于: // type UserUpdateForm = { // name?: string; // age?: number; // avatar?: string; // } function updateUser(id: number, data: UserUpdateForm) { // ... }Required<T>—— 变“全员必选”
场景:Partial的反操作。比如从后端拿来的配置对象可能有缺省,但在运行时你确保它已经补全了,可以用它断言类型。
Readonly<T>—— 变“只读”
场景:Redux 的 State 或者 Vue/React 的 Props 定义,防止在组件内部意外修改父级传递的数据。
type ReadonlyState = Readonly<{ count: number; }>; // state.count = 2; // ❌ Error: Cannot assign to 'count' because it is read-only2. 结构过滤:像筛子一样筛选类型
如果你只想从大接口中拿几个字段,或者剔除几个敏感字段,这一类工具是你的救星。
Pick<T, K>—— 只取我要的
场景:后端返回了一个包含 20 个字段的“用户详情”,但你只需要渲染一个“用户卡片”,只需要id,name,avatar。
interface UserDetail { id: number; name: string; email: string; phone: string; passwordHash: string; // 敏感信息 lastLoginIp: string; } // 只需要这三个字段做卡片展示 type UserCardProps = Pick<UserDetail, 'id' | 'name' | 'avatar'>;Omit<T, K>—— 排除我不要的
场景:创建文章列表,数据结构和创建文章表单几乎一样,但列表不需要content字段(太长了),且不需要id(新建时没有)。
interface Article { id: number; title: string; content: string; tags: string[]; } // 新建文章表单:不需要 id type CreateArticleDto = Omit<Article, 'id'>;小技巧:Omit其实可以用Pick和Exclude组合实现,但 TS 内置了它,直接用更爽。
3. 函数类型侦探:提取函数的秘密
在编写高阶组件或装饰器时,我们经常需要知道一个函数“接收什么参数”以及“返回什么类型”。
Parameters<T>—— 提取参数类型
场景:你要写一个日志装饰器,包裹任意函数,打印它的参数名和值。
function log<T extends (...args: any[]) => any>(fn: T) { return function(this: any, ...args: Parameters<T>) { console.log('Function called with:', args); return fn.apply(this, args); }; } function add(a: number, b: number) { return a + b; } const wrappedAdd = log(add); // wrappedAdd 的参数类型自动推断为ReturnType<T>—— 提取返回值类型
场景:异步请求封装时,你想基于 API 函数的返回值定义 Redux 的 Action 类型。
async function fetchUser(id: number): Promise<{ name: string; age: number }> { return { name: 'Jack', age: 18 }; } // 自动推导 fetchUser 返回的 Promise 内部结构 type UserType = Awaited<ReturnType<typeof fetchUser>>; // UserType = { name: string; age: number }4. 映射与构造:快速创建结构
Record<Keys, Type>—— 键值对工厂
场景:定义一个枚举对象,或者一个以 ID 为 Key 的字典。
// 定义一个角色权限映射 type Role = 'admin' | 'user' | 'guest'; type Permission = { read: boolean; write: boolean; }; // 快速生成对象结构:所有角色的权限列表 type RolePermissions = Record<Role, Permission>; const permissions: RolePermissions = { admin: { read: true, write: true }, user: { read: true, write: false }, guest: { read: false, write: false }, };5. 进阶实战:手写自定义工具
内置的很好用,但有时候我们需要更强大的功能。通过泛型递归和条件类型,我们可以自己造“轮子”。
实战 1:DeepPartial(深度可选)
内置的Partial只能处理一层。如果对象是嵌套的,我们需要递归地把所有层级的属性都变为可选。
type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]; }; interface Config { server: { host: string; port: number; }; db: { name: string; }; } // 现在嵌套属性也是可选的了 type PartialConfig = DeepPartial<Config>; // partialConfig.server?.host // 合法实战 2:Get(点符号访问类型)
类似 Lodash 的_.get,但是是在类型层面操作。根据字符串路径获取深层属性的类型。
type Get<T, P> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? Get<T[K], Rest> : never : P extends keyof T ? T[P] : never; interface ApiData { user: { info: { name: string; }; }; } // 根据路径 'user.info.name' 获取类型 string type UserNameType = Get<ApiData, 'user.info.name'>; // string参考官方文档:
TypeScript Utility Types 官方文档
