Reputation: 600
Can someone explain to me why in this case:
const dataValues: ValueRange[] = res.data.valueRanges.filter((range: ValueRange) => range.values);
const formattedValues: Array<SheetData | undefined> = dataValues.map(this.formatSheetRanges);
const sheetData: Array<SheetData> = formattedValues.filter((sheetData: SheetData | undefined) => sheetDataGuard(sheetData));
function sheetDataGuard(data: SheetData | undefined): data is SheetData {
return !!(<SheetData>data).user;
}
The sheetData array will still complain that the type of it is
Array<SheetData | undefined>
but if i change the last filtering to:
const sheetData: Array<SheetData> = formattedValues.filter(sheetDataGuard);
the typescript doesn't complain anymore?
Upvotes: 3
Views: 952
Reputation: 327819
It's because the typings for the method Array<T>.filter()
in the standard TypeScript lirbary have an overload signature that specifically narrows the returned array element type if the callback function is known to be a user-defined type guard function:
interface Array<T> {
filter<S extends T>(
callbackfn: (value: T, index: number, array: T[]) => value is S,
thisArg?: any
): S[];
}
Since the type of sheetDataGuard
is assignable to (value: SheetData | undefined, index: number, array: Array<SheetData | undefined>) => value is SheetData
, then calling filter
on an Array<SheetData | undefined>
with sheetDataGuard
as the callbackfn
parameter will cause the compiler to select that overload with SheetData
inferred for S
.
But when you call it with (sheetData: SheetData | undefined) => sheetDataGuard(sheetData)
instead, the type of that function is inferred to return just boolean
. That's because user-defined type guard functions do not propagate and are not inferred for you. A type like x is Y
generally gets widened to boolean
by the compiler as soon as you start doing things with it.
You could tell the compiler that your arrow function callback is also a type guard by using an arrow-function return type annotation, like this:
const sheetData: Array<SheetData> = formattedValues.filter(
(sheetData: SheetData | undefined): sheetData is SheetData =>
sheetDataGuard(sheetData)
);
And the compiler should be happy. Of course if you're going to do this, you might as well forget about defining sheetDataGuard
as a separate function:
const sheetData: Array<SheetData> = formattedValues.filter(
(sheetData: SheetData | undefined): sheetData is SheetData =>
!!sheetData
);
Anyway, hope that helps. Good luck!
Upvotes: 6