TypeScript合并多接口/类型:共享属性必填,非共享属性可选
TypeScript 合并多接口:共享属性必填,非共享属性可选
需求很明确:合并多个接口/类型后,所有被所有源接口共享的属性设为必填,仅部分接口拥有的非共享属性设为可选。
示例源接口
interface ShopUser { userId: string boughtItems: string[] } interface BlogUser { userId: string viewedBlogs: string[] } interface AppUser { userId: string appSessions: string[] }
期望生成的目标接口
interface DBUser { userId: string // 所有源接口都有,必填 boughtItems?: string[] // 仅ShopUser有,可选 viewedBlogs?: string[] // 仅BlogUser有,可选 appSessions?: string[] // 仅AppUser有,可选 }
已尝试的无效方案
- 联合类型:只能访问所有接口共有的
userId,其他属性无法直接使用type DBUser = ShopUser | BlogUser | AppUser // 仅userId可用,其余属性类型为unknown - 交叉类型:所有属性都变成必填,不符合需求
type DBUser = ShopUser & BlogUser & AppUser // 所有属性强制必填 - 手动用
Omit调整:接口复杂时会变得混乱,复用性极差,不够优雅
优雅的通用解决方案
可以通过自定义类型工具实现自动合并,无需手动调整每个属性:
// 提取所有传入接口的键的联合类型 type AllKeys<T extends object[]> = T[number] extends infer U ? keyof U : never; // 判断某个键是否存在于所有传入接口中(即是否为共享键) type IsSharedKey<T extends object[], K extends PropertyKey> = T[number] extends { [P in K]: any } ? true : false; // 核心类型:合并后共享属性必填,非共享属性可选 type MergeWithSharedRequired<T extends object[]> = { // 处理共享属性:保留原类型(必填) [K in AllKeys<T> as IsSharedKey<T, K> extends true ? K : never]: T[number] extends { [P in K]: infer V } ? V : never; } & { // 处理非共享属性:添加可选标记 [K in AllKeys<T> as IsSharedKey<T, K> extends false ? K : never]?: T[number] extends { [P in K]: infer V } ? V : never; } extends infer O ? { [K in keyof O]: O[K] } : never;
使用方式
直接传入源接口的数组即可生成目标类型:
type DBUser = MergeWithSharedRequired<[ShopUser, BlogUser, AppUser]>;
此时DBUser的类型完全符合预期:userId为必填项,其余三个属性均为可选项。
内容的提问来源于stack exchange,提问作者jovan
相关产品推荐
相关产品推荐

