补充受歧视的工会

时间:2018-12-03 06:25:26

标签: typescript discriminated-union typescript-types

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

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>}

2 个答案:

答案 0 :(得分:2)

这确实是一个错误:https://github.com/Microsoft/TypeScript/issues/28824。多亏了安德斯(Anders)和团队,下一个版本应该具有更一致的行为。

答案 1 :(得分:0)

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

编辑

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

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" }