You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 14:52:15