In strict mode, TypeScript makes it illegal to assign an array of type
(T | undefined)
to an array of type
T[]
. This prevents bugs by preventing us from dotting into (de-referencing) an undefined object. It also poses a problem: how do we convince the compiler that it is okay to assign to
T[]
after we have removed all of the undefined array values?
The answer is a user-defined type guard .
Let's start with an example. We start with an array of type
(Date | undefined)[]
and then filter out all the undefined dates.
const items: (Date | undefined)[] = [new Date(), undefined];
// Type '(Date | undefined)[]' is not assignable to type 'Date[]'.
const definedItemsBad: Date[] = items
.filter(item => typeof item !== 'undefined');
Even though we have filtered the array, the compiler cannot infer that the array has no undefined values.
Happily, a user-defined type-guard tells the compiler what we have done. In the following code, note that
input
is of type
T | undefined | null
instead of type
any
. That ensures that
T
captures only the defined part of the union type instead of capturing the whole union type of
T | undefined | null
. That is what lets our
input is T
type-guard tell the compiler that we have narrowed the type.
const isDefined = <T>(input: T | undefined | null): input is T => {
return typeof input !== 'undefined' && input !== null;
};
const definedItems: Date[] = items.filter(isDefined);
By way of explanation, here is the evolution from an inline, non-generic type-guard to the above finished product. For simplicity's sake, I have returned
true
instead of doing the actual check for
undefined
and
null
.
const definedItems1: Date[] = items.filter((input): input is Date => true);
const isDefinedDate = (input): input is Date => true;
const definedItems2: Date[] = items.filter(isDefinedDate);
const isDefinedGeneric = <T>(input: T | undefined | null): input is T => true;
const definedItems3: Date[] = items.filter(isDefinedGeneric);