为什么TS不能实现以下功能?为什么不能使用类型作为判别符?
export interface A1 {
plop: number;
}
export interface B1 {
hop: number;
}
export interface A {
foo: number;
bar: string;
inner: A1;
}
export interface B {
foo: number;
bar: string;
inner: B1;
}
export type AorB = A | B;
function test(): AorB {
let inner: A1 | B1;
if (Math.random()) {
inner = {plop: 4};
} else {
inner = {hop: 43};
}
return {
foo: 42,
bar: 'plop',
inner
};
}
TS编译器告诉我:
Type '{ foo: number; bar: string; inner: A1 | B1; }' is not assignable to type 'B'.
Types of property 'inner' are incompatible.
Type 'A1 | B1' is not assignable to type 'B1'.
Property 'hop' is missing in type 'A1' but required in type 'B1'.
考虑一下类型的扩展。类型AorB
表示联合:
{
foo: number;
bar: string;
inner: A1;
} | {
foo: number;
bar: string;
inner: B1;
}
注意这里没有出现类型A1 | B1
。这就是说,类型AorB
期望有一个对象,其中inner
属性为已知并固定为A1
或B1
。
但等待吗?既然封闭对象的属性是相同的(即foo
和bar
),上面的类型不应该等价于:
{
foo: number;
bar: string;
inner: A1 | B1;
}
从逻辑上讲,这是有道理的,您可以将内部联合分布在封闭对象类型上,并看到您将获得与AorB
相同的对象联合。事实上,这似乎是一个已知的问题,但目前TypeScript还不能做出这样的推断。
为了解决这个问题,我看到了一些选项。首先,您可以为AorB
使用单个接口,其中inner
属性的类型为A1 | B1
:
interface AorB {
foo: number;
bar: string;
inner: A1 | B1;
}
作为另一个选项,您可以修改在test
函数中构造返回对象的方式,以便向typescript清楚地表明结果对象具有固定的inner
属性:
function test2(): AorB {
let outer = {
foo: 42,
bar: 'plop',
};
if (Math.random()) {
// Clearly has type A
return {
...outer,
inner: {plop: 4}
}
} else {
// Clearly has type B
return {
...outer,
inner: {hop: 43}
}
}
}
这样,TS编译器很明显,一个分支返回类型A
,另一个返回类型B
,这与AorB
的返回类型完全匹配。
最后,因为我们人类可以看到两种类型确实是等同的,你总是可以忽略错误:
function test(): AorB {
let inner: A1 | B1;
if (Math.random()) {
inner = {plop: 4};
} else {
inner = {hop: 43};
}
// @ts-ignore: This is equivalent to the type AorB after distributing the inner union...
return {
foo: 42,
bar: 'plop',
inner
};
}