Reputation: 1378
I have the following types:
type A = { style: 'A', x: string }
type B = { style: 'B', y: string }
type Union = A | B;
I'm trying to find the element of type A
in a Union[]
, like this:
const data: Union[] = [
{ style: 'A', x: 'test' },
{ style: 'B', y: 'test' },
]
const a: A | undefined = data.find(d => d.style === 'A');
But that throws the error:
Type 'A | B | undefined' is not assignable to type 'A | undefined'.
Property 'x' is missing in type 'B' but required in type 'A'.
The predicate function for Array.prototype.find
has no problems finding the right type, I can access the properties of A
without any issues:
Is the return type that misses the refinement.
The only way I've been able to work around it is with a for loop, but that feels cumbersome and unnecessary.
let a: A | undefined;
for (let i = 0; i < data.length; i++) {
const val = data[i];
if (val.style === 'A') {
a = val;
break;
}
}
Upvotes: 1
Views: 94
Reputation: 51034
Your predicate d => d.style === 'A'
is inferred as type (d: Union) => boolean
.
For your desired behaviour, it would have to be inferred as (d: Union) => d is A
, i.e. a user-defined type guard. This won't happen because Typescript never infers that a function is a user-defined type guard; if you want it to be, you have to explicitly declare it as one. If you write it like this, then it works:
function isA(d: Union): d is A {
return d.style === 'A';
}
const a: A | undefined = data.find(isA); // no error
Actually, when I started writing this answer I going to say that this is probably impossible, because it would require Array.prototype.find
's return type to depend on whether or not the predicate is a user-defined type guard. I didn't think that was possible, but apparently it is; I only discovered it works by testing it, so I learned something today, too.
Upvotes: 2
Reputation: 3485
The signature of Array.prototype.find
is:
find<S extends T>(predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined;
Pay attention to the predicate
callback return type: value is S
. This passed-in predicate is already intended to narrow down your result. So no need to define an extra funtion, you can write it inline:
const a: A | undefined = data.find((d): d is A => d.style === 'A'); // works
Upvotes: 0