【问题标题】:Specializing Union Literal type property特化联合文字类型属性
【发布时间】:2021-10-13 13:40:53
【问题描述】:

我想要一个通用容器类型,然后我可以在运行时对其进行专门化和检查。

type AllTypes = {
    type: 'aaa' | 'bbb' | 'ccc';
    value: any;
};

type A = AllTypes & { type: 'aaa' };

const all: AllTypes = {
    type: 'aaa',
    value: 123,
};

if (all.type == 'aaa') {
    const a: A = all; // Error
}

如果我使用纯文字,它会起作用。

type AllLitTypes = 'aaa' | 'bbb' | 'ccc';

type ALit = AllLitTypes & 'aaa';

const all: AllLitTypes = 'aaa';

if (all == 'aaa') {
    const a: ALit = all; // Ok
}

在我的例子中,AllTypes 的类型文字来自一个库,所以我不能先创建特定类型然后再组合它们。

【问题讨论】:

  • TypeScript 不会自动将联合从对象属性传播到整个对象;请参阅ms/TS#8289 和其他链接到它的问题。 this code 中的任何方法对您有用吗?如果是这样,我可以写一个答案;如果不是,请详细说明失败的用例。
  • 感谢您将我指向 github 问题,在那里我找到了使行为易于理解的解释 link。我希望有一种不那么冗长的方式来实现预期的行为,但就这样吧。我最喜欢的是破坏(顺便说一句,使用它的好方法),我的第二个最喜欢的是简单的断言(因为我们都是负责任的程序员)
  • 我看到你有自己删除的答案;我不确定你是想自己写还是想让我写我的版本。无论哪种方式对我来说都很好;让我知道。
  • 不,我第一次红你评论错了。发布并获得信用
  • 顺便说一句,当我应该使用“传播”时,我使用了“解构”这个词;它们是相互对立的

标签: typescript


【解决方案1】:

编译器没有做很多工作来尝试将union types 向上传播到对象属性之外或向下传播到它们中。也就是说,编译器通常不会看到类型 {foo: A} | {foo: B}{foo: A | B} 是等价的。您可以查看microsoft/TypeScript#8289microsoft/TypeScript#12052 和其他相关问题以获得规范答案,但问题是以正确的方式进行此类类型操作通常非常昂贵。在 TypeScript 3.5 some support was added for this kind of union transformation 但它只适用于有限的情况,你的代码没有这样做。

由于这不会发生,您的AllTypes 类型不会被视为discriminated union,因为它根本不是联合(只有type 属性是联合)。所以检查alltype 属性只能缩小type 的类型,而不是all 的类型。


我将采取的各种方法来解决这个问题:

传播:

if (all.type == 'aaa') {
    const a: A = { ...all, type: all.type };
}

编译器知道all.type 属于literal type "aaa",并且object spreading 将允许编译器将all 的已知非缩小类型与{type: all.type} 的缩小类型结合起来。

类型断言:

你知道一些编译器不知道的事情,所以现在是使用type assertion 告诉它的合理时机。您需要小心自己没有撒谎,但只要您确定自己说的是真话,这是防止错误的简单方法:

if (all.type == 'aaa') {
    const a = all as A; 
}

转换为有区别的联合:

您可以让编译器将属性为联合类型的对象类型转换为该属性不是联合类型的对象类型的联合:

type DistributeUnionProp<T, K extends keyof T> =
    K extends unknown ?
    T[K] extends infer U ? U extends unknown ?
    { [P in keyof T]: P extends K ? U : T[P] } :
    never : never : never;

type DiscriminatedAllTypes = DistributeUnionProp<AllTypes, "type">
/* type DistribAllTypes = {
    type: "aaa";
    value: any;
} | {
    type: "bbb";
    value: any;
} | {
    type: "ccc";
    value: any;
} */

通过对AllTypes 这样做,我们得到了一个有区别的联合,我们可以将all 扩大到(在 TS3.5 支持之后):

const discrimAll: DiscriminatedAllTypes = all;

一旦我们这样做了,discrimAll 就是一个有区别的联合,可以通过检查区别属性 type 来缩小范围:

if (discrimAll.type === 'aaa') {
    const a: A = discrimAll;
}

这些都有自己的优势。

Playground link to code

【讨论】:

  • 优秀而全面的答案!
猜你喜欢
  • 2018-08-27
  • 1970-01-01
  • 1970-01-01
  • 2019-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-19
相关资源
最近更新 更多