Why is the following not possible for TS ? Why cant I use a type as discriminant ?
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
};
}
The TS compiler tells me :
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'.
Consider for a moment the expansion of your types. The type AorB
represents the union:
{
foo: number;
bar: string;
inner: A1;
} | {
foo: number;
bar: string;
inner: B1;
}
Noting that nowhere in this does the type A1 | B1
A1 | B1
appear. This is to say that the type AorB
is expecting to have an object where the inner
property is known and fixed as either A1
or B1
.
But wait? Since the properties of the enclosing object are the same (namely foo
and bar
), shouldn't the type above be equivalent to:
{
foo: number;
bar: string;
inner: A1 | B1;
}
Logically, that makes sense, you can distribute the inner union over the enclosing object type and see that you would get the same union of objects that you have for AorB
. And in fact it looks like this is a known issue , but currently TypeScript isn't able to make this inference.
To fix this, I see a few options. First, you can just use a single interface for AorB
where the inner
property is given the type A1 | B1
A1 | B1
:
interface AorB {
foo: number;
bar: string;
inner: A1 | B1;
}
As another option, you can modify the way in which you construct the returned object in your test
function to clearly indicate to typescript that the resulting object has a fixed inner
property:
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}
}
}
}
This way, it is obvious to the TS compiler that one branch returns type A
and the other type B
, which matches cleanly to the return type of AorB
.
And finally, since we humans can see that the two types are in fact equivalent, you can always just ignore the error:
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
};
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.