TypeScript新Extract/Exclude条件类型的实用场景问询
嘿,我完全懂你为啥一开始会觉得Exclude和Extract有点“摸不着头脑”——毕竟Pick、Omit这些直接操作对象属性的内置类型太常用了,而这俩看起来只跟联合类型挂钩,好像离日常开发有点远。但其实它们在处理联合类型的细化、过滤时特别实用,给你举几个肯定能用上的场景:
1. 状态管理:筛选特定状态类型
在React、Vue的状态管理(比如useReducer、Pinia)里,我们经常会定义包含多种状态的联合类型,这时候Extract/Exclude能快速帮我们筛选出需要的状态:
// 定义API请求的三种状态 type SuccessResponse = { type: 'success'; data: User[] }; type ErrorResponse = { type: 'error'; message: string }; type LoadingState = { type: 'loading' }; type RequestState = SuccessResponse | ErrorResponse | LoadingState; // 提取仅成功状态的类型 type OnlySuccess = Extract<RequestState, { type: 'success' }>; // 结果:{ type: 'success'; data: User[] } // 排除加载状态,只保留结果/错误状态 type NonLoadingStates = Exclude<RequestState, LoadingState>; // 结果:SuccessResponse | ErrorResponse
当你需要写只处理成功状态的逻辑时,用OnlySuccess就能精准约束类型,避免处理其他状态的冗余代码。
2. 路由系统:筛选特定格式的路由
如果你的项目用了字符串联合类型定义路由,结合模板字面量类型,Extract/Exclude能快速筛选出符合规则的路由:
type AllRoutes = '/home' | '/user/profile' | '/user/settings' | '/admin/dashboard' | '/admin/users'; // 提取所有/user开头的用户路由 type UserRoutes = Extract<AllRoutes, `/user/${string}`>; // 结果:'/user/profile' | '/user/settings' // 排除/admin相关的后台路由,得到前端公共路由 type PublicRoutes = Exclude<AllRoutes, `/admin/${string}`>; // 结果:'/home' | '/user/profile' | '/user/settings'
这个场景在写路由守卫、生成菜单列表时特别好用,不用手动枚举符合条件的路由。
3. 类型细化:移除可选类型中的undefined
当你有一个包含undefined的联合类型(比如可选参数、可选属性的类型),Exclude可以快速帮你得到必填的类型:
// 工具类型:移除联合类型中的undefined type RequiredValue<T> = Exclude<T, undefined>; // 可选的用户名类型 type MaybeUsername = string | undefined; // 得到必填的用户名类型 type Username = RequiredValue<MaybeUsername>; // string // 另一个例子:提取仅鼠标事件类型 type AllEvents = MouseEvent | KeyboardEvent | TouchEvent; type MouseOnlyEvents = Extract<AllEvents, { clientX: number }>; // 结果:MouseEvent(只有它包含clientX属性)
在处理事件回调、表单参数时,这种类型细化能让你的类型约束更精准。
4. 结合其他工具类型:处理对象联合的属性筛选
把Extract和Pick、Omit结合,能解决更复杂的对象联合类型处理问题:
type Product = | { type: 'book'; title: string; pages: number } | { type: 'movie'; title: string; duration: number } | { type: 'music'; title: string; artist: string }; // 先提取所有带pages属性的产品(也就是书籍),再筛选title和pages属性 type BookInfo = Pick<Extract<Product, { pages: number }>, 'title' | 'pages'>; // 结果:{ title: string; pages: number } // 排除音乐类型,得到书籍和电影的联合类型 type NonMusicProducts = Exclude<Product, { type: 'music' }>; // 结果:{ type: 'book'; ... } | { type: 'movie'; ... }
单独用Pick只能处理单一对象类型,而Extract先帮我们从联合里筛选出目标对象,再结合Pick就能精准提取需要的属性。
总的来说,Exclude和Extract的核心是对联合类型做“筛选”和“剔除”,日常开发里联合类型的使用场景比你想象的多——状态管理、路由、API响应、事件处理这些地方都能用上,把它们和其他类型工具结合,能解决很多复杂的类型细化问题。
内容的提问来源于stack exchange,提问作者Aston Richworth

