让我们考虑以下代码。我有一个 ADT,其中具体类型之间的区别是 kind
属性。我想要一个实用方法来创建这些类的实例。
interface I {
id: number;
}
interface A extends I {
kind: "a";
}
interface B extends I {
kind: "b";
}
type O = A | B;
function create(id: number, kind: "a" | "b"): O {
return {
id: 1,
kind: kind
};
}
显然上面的代码给出的错误为 "a"| “b”
不可分配给 “a”
或 “b”
。有没有办法表达这个功能?以某种方式表达类型 oneOf "a"| “b”
?
最佳答案
更新:2019 年 5 月 30 日,随着 TypeScript 3.5 的发布,此问题应由 smarter union type checking 解决。以下内容适用于 3.4 及以下版本:
我认为您已经正确表达了代码,并且 TypeScript 正在阻止有效的分配。对于你我来说,显然 O
在结构上应该与
type OPrime = {
id: number;
kind: "a" | "b"
}
由于 create()
返回一个 OPrime
类型的值,因此它应该可以分配给 O
。唉,TypeScript 将 O
视为可分配给 OPrime
,但反之则不然。
最简单的解决方法是使用类型断言:
function create(id: number, kind: "a" | "b"): O {
return {
id: 1,
kind: kind
} as O; // assertion
}
类型断言通常并不安全,但当您了解的表达式类型超出了编译器所能计算出的范围时,类型断言非常有用。但是很难区分编译器警告您的合法错误和编译器无法识别有效表达式之间的区别。在这种情况下,我相当确定您的做法是正确的,并且编译器正在报告错误的误报。
结果是intended behavior对于 typescript 。虽然 O
和 OPrime
恰好相同,但编译器需要额外的工作才能识别这一点。 通常这是 not warranted ,因为两个联合组成部分很少仅因单个同名属性的类型而有所不同。在大多数情况下(甚至可能是您实际的非玩具用例),您的 A
和 B
类型中会有其他类型。例如:
interface AExtra extends I {
kind: "a";
name: string;
}
interface BExtra extends I {
kind: "b";
age: number;
}
type OExtra = AExtra | BExtra;
现在不再可能将 OExtra
表示为具有联合值属性的单个非联合类型。考虑OExtraPrime
:
type OExtraPrime = {
id: number;
kind: "a" | "b";
name?: string;
age?: number;
}
确实,任何 OExtra
都可以分配给 OExtraPrime
,但反之则不然。因此,由于在一般实践中,O
和 OPrime
之间的等价关系不会出现,因此编译器不会费心去建立它。所以你能做的最好的事情就是类型断言。
希望这有帮助;祝你好运!
关于Typescript 使用鉴别器的联合字符串类型创建 ADT(可鉴别联合类型)的实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47018496/