打字稿:检查并集类型是否相交

时间:2019-02-27 16:32:58

标签: typescript union-types

我有一堆具有一个公共字段的接口,用作不相交联合的区分符。该字段由其他地方使用的几个枚举组成,所以我不能以合理的方式使其成为单个枚举。这样的东西(简化):

const enum Enum1 { key1 = 'key1', key2 = 'key2' }
const enum Enum2 { key1 = 'key1', key3 = 'key3' }
interface Item1 { key: Enum1; value: string; }
interface Item2 { key: Enum2; value: number; }
type Union = Item1 | Item2;

该类型的用法如下:

let item: Union = getUnionValue();
switch (item.key) {
    case Enum1.key1:
        // do something knowing the 'item.value' is 'string'
        break;
    // some other cases
    case Enum2.key1:
        // do something knowing the 'item.value' is 'number'
        break;
    // some more cases
}

当然,当不同枚举中的键等效时,将导致运行时损坏。

有没有办法检查鉴别符类型Union['key']是否实际上是不相交的,即所使用的所有类型是否都是不相交的?换句话说,我正在寻找会在上述类型上出错的代码,表示Enum1.key1Enum2.key1发生冲突。

我尝试了以下操作:

type Checker<T> = T extends any ?
 (Exclude<Union, T>['key'] extends Extract<Union, T>['key'] ? never : any)
 : never;
const test: Checker<Union> = null;

希望利用条件类型上的分布,但这似乎不起作用。

2 个答案:

答案 0 :(得分:1)

这里是一种使用IsUnion的解决方案,而该解决方案又依赖于UnionToIntersection

type UnionToIntersection<U> =
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type IsUnion<T> =
    [T] extends [UnionToIntersection<T>] ? false : true
type DisjointUnion<T extends { key: string }> =
    { [k in T["key"]]: IsUnion<Extract<T, { key: k }>> } extends { [k in T["key"]]: false } ? T : never;

使用具有不同的key值的并集是可以的:

type Union = DisjointUnion<Item1 | Item2>;         // Item1 | Item2

但是添加具有现有键的项,结果类型将为never

type Union = DisjointUnion<Item1 | Item2 | Item3>; // never

答案 1 :(得分:0)

一种可能性涉及基于地图/对象类型构建联合,因此不可能添加具有冲突键的东西:

interface Item1 { key: 'key1'; value: string; }
interface Item2 { key: 'key2'; value: number; }
interface Item3 { key: 'key1'; value: Object; }

type UnionAsMap = {
    'key1': Item1,
    'key2': Item2
};
type Union = UnionAsMap[keyof UnionAsMap]; // Equivalent to Item1 | Item2

这将带来一个问题,您可能会不小心设置了错误的密钥,但是我们可以使用以下代码来强制要求它们的密钥匹配:

type UnionWithMatchingKeys = { [K in keyof UnionAsMap]: UnionAsMap[K] extends { key: K } ? UnionAsMap[K] : never };
type UnionHasMatchingKeys = Union extends UnionWithMatchingKeys[keyof UnionWithMatchingKeys] ? true : false;
let unionCheckSuccess: UnionHasMatchingKeys = true;

因此,如果您不小心做了这样的事情:

interface Item1 { key: 'key3'; value: string; }

UnionHasMatchingKeys将最终为false,而unionCheckSuccess行将显示错误。

相关问题