TypeScript:防止联合成员重叠

问题描述 投票:0回答:2

我正在尝试定义一个类型,它是许多联合字符串的联合。

例如。

type SubUnion1 = "one" | "two"
type SubUnion2 = "two" | "three"
...
type SubUnionN = "nine" | "ten"

type Union = SubUnion1 | SubUnion2 | ... | SubUnionN

如果任何子联合有重叠的字符串,我希望 TS 抛出错误。 在此示例中,

SubUnion1
SubUnion2
"two"
重叠,这应该会引发错误。

我知道这可以使用以下方法在两种类型之间完成:

type NoOverlap<A, B> = Extract<A, B> extends never ? A & B : never

但是,我想不出一种优雅的方法来做到这一点将超过 2 种类型。

typescript
2个回答
4
投票

我不知道你是否认为这种方法“优雅”,但至少它不需要你自己写出每对

SubUnionX
元素:

首先创建子联合的元组,以便编译器可以在将它们放在一起之前以编程方式处理它们

Union
并失去对不同子联合的跟踪:

type SubUnions = [SubUnion1, SubUnion2, /* ... */ SubUnionN]

然后我们可以创建一个类型,如果没有重叠,则其计算结果为

never
,否则它会生成元组的并集,其中包含有关重叠位置的信息:

type Overlaps<T extends any[]> = { [K in keyof T]: { [L in keyof T]:
    L extends K ? never :
    (T[K] & T[L]) extends never ? never :
    ["Elements at indices", K | L, "both contain", T[K] & T[L]]
}[number] }[number]

本质上,我们对元组进行两次映射来创建一个 N×N 矩阵。 我们查看索引为 (K

,
L
) 的每个元素,如果 
T[K]
T[L]
 重叠,则我们在矩阵中放入一个条目。  然后我们将矩阵中的所有条目作为一个并集。

请注意,条目

["Elements at indices", K | L, "both contain", T[K] & T[L]]

 从技术上讲只是一个具有四个元素的元组类型。  第一个和第三个元素是字符串文字类型,第二个和第四个元素分别是重叠键和重叠属性类型。  这种类型在运行时实际上并不能作为任何 
value 的类型。 我们并没有考虑制作一个像这样的四元素数组。 它实际上只是以一种我们希望能产生有用的错误消息的方式布置的东西。 换句话说,它是一个权宜之计 “无效类型”(目前 TypeScript 中不存在该功能,但最终可能会以类似 throw
 类型
的形式实现)。

最后,如果您希望在存在重叠时出现实际的编译器错误,我们可以创建一个仅接受

never

 的类型函数。

type ExpectNever<T extends never> = void;
最后我们把它们放在一起,如下所示:

type EnsureNoOverlapsOfSubUnions = ExpectNever<Overlaps<SubUnions>>; // error! // Type '["Elements at indices", "0" | "1", "both contain", "two"]' // does not satisfy the constraint 'never'.
希望错误消息足以帮助解决问题。

哦,如果您将

SubUnions

 作为元组,则只需获取其 
Union
 索引属性即可获得原始 
number

type Union = SubUnions[number];

Playground 代码链接


0
投票
/** * Given a tuple of types, if there are duplicates keys among any subset of * those types, this resolves to some (not necessarily all) of those duplicate * keys, else this resolves to 'never'. */ type SomeDuplicateKeys<T extends any[]> = T extends [infer H1, infer H2, ...infer R] ? keyof H1 & keyof H2 extends never ? SomeDuplicateKeys<[H1 & H2, ...R]> : keyof H1 & keyof H2 : never; type UnionNoDuplicateKeysAllowed<T extends Object[]> = SomeDuplicateKeys<T> extends never ? T[number] : never;
    
© www.soinside.com 2019 - 2024. All rights reserved.