【TS】如何在 typescript 中合并一个 interface union?
admin
2024-04-25 00:09:13
0

mixin interface union in typescript

今天在一个 typescript 类型系统的设计问题上卡了一晚上。

简单来讲:

type origin = {'typ1': {num: number,str: string,},'type2': {num: string,bool: boolean,str: string,},
}type Transformer = /* your code here */;// except
type result = Transformer
// equals
type result = {num: number | string,str: string,bool: boolean,
}
type keys = keyof result; 
// 'num' | 'str' | 'bool'

简单说,我想将一个对象的 sub 对象 intersect 在一起,我当然可以直接写 type result = origin['type1'] & origin['type2'],但我的 origin 可能有很多 sub object,且后续可能要继续增加,如果是别人接手了我的代码,我希望这里是自动化的、关联的。

我在尝试中,实现了对 object tuple 的 intersection,但我很难将 origin 转化为 tuple。

直接使用 origin[keyof origin] 则会得到一个 union type,此时 keyof 这个 union type 只会得到它们的共有 key。

所以我有两个方向,一是将 origin 转化为 sub object 的 tuple,另一个方向是将 union type 转化为 intersection type。

后者有一个解决方案:

type U2I = 
(U extends any ? (u: U) => void : never
) extends (i: infer I) => void ? I : never;

这个非常巧妙,其实最初 copilot 和 chatGPT 都帮我把这个 type 补全出来了,但我因为看不懂,以为是 ai 的什么坏 case,就没理会。

这玩意利用了 union type 在 conditional type 中的 distributive 的特性,将 union 在第一个 condition 中 map 到多个具有单独类型参数 function,然后再 infer 到函数的参数上,就从 union 变成了 intersection。

为什么这么怪?看这个 case:

type example = U2I<{ str: string } | { num: number }>;
// 向前推一步
((param: { str: string }) => void| ((param: { num: number }) => void)
) extends (i: infer I) => void ? I : never

相当于将 u1 | u2 => (u1) => void | (u2) => void,然后再 infer 到同一个函数的参数上。

我们最终得到的是 (i: infer I) => void 函数中的参数,在上面的例子中,这个函数实际上是将 union 中的两个函数合在了一起。

思考一下,假设 union 中的两个函数的实现分别为:

// first fun of the union
function(param: { str: string }) {assert(typeof param.str === 'string')
}
// second
function(param: { num: number }) {assert(typeof param.num === 'number')
}

那么将两个函数的实现合在一起,就是:

function(param: /* ignore temporarily */) {assert(typeof param.str === 'string')assert(typeof param.num === 'number')
}

对上面这个合并的函数来说,我们要求他的参数类型一定是同时满足 union 中所有函数的参数类型的,所以一定是取交。

于是我们就得到了 intersection { str: string, num: number }

但如果你尝试 U2I 会得到 never,因为显然一个变量不可能即是 number 又是 string,这放到嵌套的 object 中也是一样,存在任意冲突时会整体返回 never,所以简单的取交是不可取的。

如何处理冲突?我们希望共有字段能够在内部自动 union,我们首先需要一个工具:

type DistributeForGetValueTypeUnion = U extends { [key in K]: unknown } ? U[K] : never

它可以从 interface union 中 pick 出指定 key 的类型 union,例如:

type example = DistributeForGetValueTypeUnion<{a: number, b: string 
} | {a: string 
}, 'a'> // number | string

它是这样工作的,根据 distributive 这个特性,{ a: number, b: string }{ a: string } 分别被判断是否存在目标 key,若存在则单独取 T[K],否则 never 认为该类型不存在,最后会把所有 T[K] union 到一起。

下面就比较简单了,只需要从所有 key 中全部 maping 到 DistributeForGetValueTypeUnion 从而重建一个 interface 即可。

type Keys = U extends infer T ? keyof T : never;
type Mixin = {[key in Keys]: DistributeForGetValueTypeUnion
}

我们还实现了一个 Keys 类型,它用来获取一个 interface union 中的所有 key,这里也是 distribute 每个 union 元素,然后单独取 keyof,最后取 union。

相关内容

热门资讯

白鸽在线赴港上市获31.5倍超... 保险科技赛道再迎资本市场新军。6月24日,白鸽在线(厦门)数字科技股份有限公司(下称“白鸽在线”)在...
蚂蚁阿福发0.01元体脂秤要让... 严格落实一人一秤编辑|卢力麟 作者|Vista氢商业设计|胖兔一代人有一代人的鸡蛋要领,现在最火的...
一周内新增6单商业不动产REI... 记者丨唐韶葵编辑丨吴抒颖 张伟贤6月18日,国内首批4单商业不动产REITs在上交所挂牌上市,引发市...
露笑科技、TCL中环、万通发展... 周五在科技板块回调和传统权重板块继续下跌的影响之下,大盘盘中大跌超100点。从目前来看,传统板块走弱...
公司与字节跳动是否有合作或者业... 有投资者向嘉环科技(603206.SH)提问,公司与字节跳动是否有合作或者业务上的往来?在哪些方面?...
世界杯“名场面”,居然是AI造... AI到底还是太好用了,好用到伴随世界杯出现了“AI的疯狂世界杯平行宇宙”。在这个宇宙,看台上总是有惊...
全球性涨价,苹果“崩了”! 苹... 美国方面当地时间25日消息,苹果公司宣布上调全球多个市场的Mac、iPad等多款硬件产品价格,涨幅在...
千亿大族激光拟豪掷25亿加码光... 本文来源:时代周报 作者:宋逸霆、韩迅千亿PCB“卖铲人”大族激光(002008.SZ)正在大举挺进...
外交部:希望日本为政者倾听正视... 中国青年报客户端北京6月26日电(中青报·中青网记者 贾晓静)关于日本企业及经济界团体报名参加中国国...
“反腐少将”被查,朝鲜领导层重... 据朝中社6月23日消息,朝鲜劳动党九届二中全会于6月20日至22日举行,劳动党总书记金正恩出席会议并...