Issue
The return type of Array.find is a list of all possible matches. I am wondering if I can narrow it down.
Consider the following situation:
type MyArray = [{type: 'big', some: true}, { type: 'small', other: false}];
const findInArr = <T extends MyArray, Q extends MyArray[number], R extends Q['type']>(arr: T, type: R) => {
return arr.find((i) => i.type === type)
}
const result = findInArr(theArray, 'big'); // Should have the type {type: 'big', some: true}
I have been playing around the whole day but can't figure this one out.
I have succeeded by type casting and by using type guards, however I am wondering if there is a more safe and elegant way.
Anything that points me in the correct direction is appreciated. Thank you!
Solution
You need an explicit return type that is created from your generic types.
That looks like it might be this:
Extract<T[number], { type: R }> | undefined
Extract
returns only the types from a union (first parameter) that match a type (second parameter). So this resolves to only the array members that have the type
property that was passed in.
So that looks like this:
const findInArr = <
T extends Arr,
R extends T[number]['type']
>(
arr: T,
type: R
): Extract<T[number], { type: R }> | undefined => {
return arr.find((i) => i.type === type)
}
(Note that I removed Q
, as it's a useless generic type that is trivially derived from what is already there)
And it seems to work when used:
declare const theArray: MyArray
const result = findInArr(theArray, 'big'); // {type: 'big', some: true}
if (result) result.some // fine
But there's one type error still, inside this function on the return
.
Type '{ type: string; } | undefined' is not assignable to type 'Extract<T[number], { type: R; }> | undefined'.
Type '{ type: string; }' is not assignable to type 'Extract<T[number], { type: R; }>'.(2322)
The built in find
method on arrays is typed to return a union type of all array members. But you can use a type predicate function passed to find
that tells TypeScript what returning true
means for the resulting type.
So you can add i is Extract<T[number], { type: R }>
to the return type of the find callback and everything works.
const findInArr = <
T extends Arr,
R extends T[number]['type']
>(
arr: T,
type: R
): Extract<T[number], { type: R }> | undefined => {
return arr.find(
(i): i is Extract<T[number], { type: R }> => {
return i.type === type
}
)
}
Answered By - Alex Wayne
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.