【问题标题】:Typescript: check union type for being disjoint打字稿:检查联合类型是否不相交
【发布时间】:2019-02-28 10:59:39
【问题描述】:

我有一堆带有一个公共字段的接口,用作不相交联合的鉴别器。该字段由其他地方使用的几个枚举组成,因此我无法以合理的方式使其成为单个枚举。像这样(简化):

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;

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

【问题讨论】:

  • 只是好奇为什么此时需要检查这个? key1 会缩小到 value: string | object 还不够吗?
  • 在问题本身中回答(包括示例用法)。

标签: typescript union-types


【解决方案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

【讨论】:

  • 非常聪明,谢谢。但是由于枚举(不仅是文字)键,我的用例似乎更难使用,这些键在文字中提到,但在代码中简化了。我稍后会更新问题。
  • @Cerberus 我不确定它是否可以用enum 完成,因为它们不能很好地与类型运算符一起使用(例如Enum1 &amp; Enum2 评估为never 而不是@ 987654333@)。我还在研究它是否可以通过多个联合来完成。
【解决方案2】:

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

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 行会出现错误。

【讨论】:

    猜你喜欢
    • 2019-05-26
    • 1970-01-01
    • 2015-05-24
    • 2020-03-16
    • 1970-01-01
    • 2019-11-05
    • 1970-01-01
    • 2022-01-13
    • 2020-08-05
    相关资源
    最近更新 更多