【问题标题】:Taking complements of discriminated unions采取歧视性工会的补充
【发布时间】:2019-05-04 10:30:29
【问题描述】:

假设我有以下区分联合和一些关联类型

type Union = 'a' | 'b';
type Product<A extends Union, B> = { f1: A, f2: B};
type ProductUnion = Product<'a', 0> | Product<'b', 1>;

现在我可以通过使用映射类型和Exclude来获取补码

type UnionComplement = {
  [K in Union]: Exclude<Union, K>
};
// {a: "b"; b: "a"}

type UnionComplementComplement = {
  [K in Union]: Exclude<Union, Exclude<Union, K>>
};
// {a: "a"; b: "b"}

到目前为止,所有这些都是有道理的,但是当我尝试采用双重补码时,ProductUnion 的事情就崩溃了。第一个补码效果很好

type ProductComplement = {
  [K in Union]: Exclude<ProductUnion, { f1: K }>
};
// {a: Product<'b', 1>; b: Product<'a', 0>}

无论我尝试什么,双补码都不正确

type ProductComplementComplement = {
  [K in Union]: Exclude<ProductUnion, Exclude<ProductUnion, { f1: K }>>
};
// {a: ProductUnion; b: ProductUnion}

我不明白错误在哪里,因为如果我替换类型,那么它应该可以工作。 K 取双补时只有 2 个值,所以让我们尝试第一个

type First = Exclude<ProductUnion, Exclude<ProductUnion, { f1: 'a' }>>;
// {f1: 'a'; f2: 0}

第二个也可以

type Second = Exclude<ProductUnion, Exclude<ProductUnion, { f1: 'b' }>>;
// {f1: 'b'; f2: 1}

所有组成部分都可以工作,但在映射类型中组合时,它似乎崩溃了。我在这里错过了什么?

一时兴起,我尝试添加一个类型参数,以通过抽象补充过程来查看会发生什么

type Complementor<T> = {
    [K in Union]: Exclude<T, { f1: K }>
};

type DoubleComplementor<T> = {
    [K in Union]: Exclude<T, Exclude<T, { f1: K }>>
};

现在,如果我将参数化类型应用于 ProductUnion,它将完全按照我的预期工作

type Complement = Complementor<ProductUnion>;
// {a: Product<'b', 1>; b: Product<'a', 0>}

type DoubleComplement = DoubleComplementor<ProductUnion>;
// {a: Product<'a', 0>; b: Product<'b', 0>}

【问题讨论】:

  • 这是非常奇怪的行为,据我所知,编译器停止分发 Exclude 的 T 参数 .. 不知道为什么 .. 可能是一个错误。
  • 奇怪...我找不到任何关于嵌套分布式类型无法像这样工作的 GitHub 问题。就好像编译器在进行分发之前过于激进地简化了一些事情。可能值得提出问题。
  • 谢谢各位。我将在今天晚些时候打开一个问题来跟进。
  • 那么@TitianCernicova-Dragomir @jcalz 这里的预期/正确输出是什么? ProductComplementComplement 或 DoubleComplement 之一
  • @AravindanVe OP 所期望的行为也是我所期望的。

标签: typescript discriminated-union typescript-types


【解决方案1】:

这确实是一个错误:https://github.com/Microsoft/TypeScript/issues/28824。感谢 Anders 和团队,下一个版本应该有更一致的行为。

【讨论】:

    【解决方案2】:

    抽象类型别名的行为方式不应该与内联别名完全相同。我认为这就是重点。

    编辑:

    好吧,它看起来确实像一个错误。

    type E1 = Exclude<{ f1: 'a' } | { f1: 'b' },
                Exclude<{ f1: 'a' } | { f1: 'b' }, { f1: 'a' }>>;
    
    // E1 = { f1: "a" }
    
    type E2<K> = Exclude<{ f1: 'a' } | { f1: 'b' },
                   Exclude<{ f1: 'a' } | { f1: 'b' }, { f1: K }>>;
    
    // E2<K> = { f1: "a" } | { f1: "b" }
    //         ^ no `K` in the resulting type
    //         the compiler has somehow eliminated `K` from the resulting type
    
    // no matter what you do from here on, doesn't get re-evaluated with K in it.
    //
    type E2a = E2<'a'>;
    // E2a = { f1: "a" } | { f1: "b" }
    
    type E2b = E2<'b'>;
    // E2b = { f1: "a" } | { f1: "b" }
    

    【讨论】:

    • 你知道distributive conditional types吗? “抽象”和“内联”类型别名逻辑之间的区别应该是,当条件类型作用于裸类型参数名称时,它将通过联合分配条件类型,否则不会。这是意料之中的。我们没想到的是Exclude&lt;&gt; 在以嵌套方式使用时会突然停止分发。正如@TitianCernicova-Dragomir 在评论中所说,这可能是一个编译器错误。
    • 好吧,我不知道这个!
    • @jcalz 谢谢!我不知何故错过了关于分布式条件类型的整个部分。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-17
    • 2015-01-22
    • 1970-01-01
    • 2017-07-24
    • 2013-09-30
    • 2017-06-08
    • 1970-01-01
    相关资源
    最近更新 更多