考虑这段代码:
function Foo(num: number) {
switch (num) {
case 0: return { type: "Quz", str: 'string', } as const;
case 1: return { type: "Bar", 1: 'value' } as const;
default: throw new Error("Unknown discriminant: " + num);
}
}
打字稿推断出这个可区分的联合类型:
function Foo(num: number):
{ readonly type: "Quz"; readonly str: "string"; readonly 1?: undefined; } |
{ readonly type: "Bar"; readonly 1: "value"; readonly str?: undefined; }
但是我不想得到打字稿推断的歧视性联盟类型,但期望这样:
{ type: "Quz"; str: "string"; } | { type: "Bar"; 1: "value"; }
我不想单独指定返回类型。另外,我不想提前评估任何可能的输出。
有没有办法提示打字稿编译器猜测我期望的判别联合的类型?
TypeScript 在推断值的类型时使用各种启发式规则,选择这些规则是为了为广泛的用例提供理想的行为......但总会有一些人和情况下启发式不可避免地无法满足期望。
其中一个规则是,对象文字的联合是通过联合其他成员的可选
undefined
属性推断出来的,如 TypeScript 2.7 发行说明 中所述以及 microsoft/TypeScript#19513 中的实现。 这使得原本难以处理的联合(例如 {foo: string} | {}
,您无法使用 foo
进行索引)变成了 受歧视联合(例如 {foo: string} | {foo?: undefined}
)。 但不幸的是,对于您的示例,这会导致您不想要的类型。
一般来说我建议有人手动注释他们期望的类型,如果推理不能按他们想要的方式工作,编译器将使用它作为上下文:
type DiscU = { type: "Quz"; str: "string"; } | { type: "Bar"; 1: "value" };
function fooAnnotate(num: number): DiscU {
switch (num) {
case 0: return { type: "Quz", str: 'string', };
case 1: return { type: "Bar", 1: 'value' };
default: throw new Error("Unknown discriminant: " + num);
}
}
在上面,输出类型正是您想要的(因为它是手动注释的),您甚至不需要
const
断言,因为所需的类型会在上下文中键入这些对象文字。
不幸的是,要求您不要手动写出类型,因此我们将放弃这种方法。
因为您不喜欢的行为仅在使用对象字面量时发生,所以“标准”解决方法与您用来避免过多属性检查的解决方法相同:将对象字面量分配给中间变量,然后构建与变量联合:
function foo(num: number) {
const case0 = { type: "Quz", str: 'string' } as const;
const case1 = { type: "Bar", 1: 'value' } as const;
switch (num) {
case 0: return case0;
case 1: return case1;
default: throw new Error("Unknown discriminant" + num);
}
}
/* function foo(num: number): {
readonly type: "Quz";
readonly str: "string";
} | {
readonly type: "Bar";
readonly 1: "value";
} */
readonly
;我们稍后会回来讨论......)
万岁! 但这涉及到预先计算每个可能输入的输出,并且要求您不要这样做。 所以我们需要修改这种方法。
避免预计算的一种方法是用返回该值的立即执行函数替换该值。 您可以内联执行此操作,甚至将其移回到
switch
/case
语句中:
function foo(num: number) {
switch (num) {
case 0: return (() => ({ type: "Quz", str: 'string' } as const))();
case 1: return (() => ({ type: "Bar", 1: 'value' } as const))();
default: throw new Error("Unknown discriminant" + num);
}
}
/* function foo(num: number): {
readonly type: "Quz";
readonly str: "string";
} | {
readonly type: "Bar";
readonly 1: "value";
} */
中间函数设法阻止可选的
undefined
属性,并满足您的其余需求。 万岁!
嗯,属性仍然是
readonly
。 如果这很重要,您可以将立即执行的函数更改为独立函数,该函数返回其输入的非readonly
版本(使用带有-readonly
修饰符的映射类型):
function foo(num: number) {
const mutable = <T extends object>(o: T): { -readonly [K in keyof T]: T[K] } => o;
switch (num) {
case 0: return mutable({ type: "Quz", str: 'string' } as const);
case 1: return mutable({ type: "Bar", 1: 'value' } as const);
default: throw new Error("Unknown discriminant" + num);
}
}
/* function foo(num: number): {
type: "Quz";
str: "string";
} | {
type: "Bar";
1: "value";
} */
现在您已经完全得到了您想要的类型,无需将其写出或预先计算函数的任何输出值。 😅