Example 1
const myNumber = _.sample([1, 2, 3]);
// Expected type: number
// Actual type: number
Example 2
const arr = [1, 2, 3]
const myNumber = _.sample(arr);
// Expected type: number
// Actual type: number | undefined
Why is Typescript giving the type number | undefined
in the second case, but not the first case?
This is happening because in one case the length of the array is known, and in the other it is not. If the length might be 0, then undefined
is a possible output.
When you do const arr = [1, 2, 3]
, the type on arr
is number[]
. That means it's an array of numbers which can grow and shrink. You and I can see that it's not going to shrink to 0-length before the next line, but the type information doesn't include that knowledge. So when it gets passed into sample, the types say that undefined is a possible result.
When you create and use the array in the exact same spot, as in _.sample([1, 2, 3]);
, typescript is able to assume a stricter type because it knows how it is being used. The type is inferred to be [number, number, number]
, ie a tuple of length 3, and therefore an element will definitely be returned.
If you want to make this happen with the array being on a separate line, you'll need to tell typescript you want a stricter type on arr
. The simplest way to do this is with as const
, which tells typescript you won't be changing this array:
const arr = [1, 2, 3] as const; // readonly [number, number, number]
const myNumber = _.sample(arr); // number
You can see the type definitions for sample
here: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/lodash/common/collection.d.ts#L1641 . The tuple case is handled by this type:
sample<T>(collection: readonly [T, ...T[]]): T;
And the array case is handled by this type:
sample<T>(collection: Dictionary<T> | NumericDictionary<T> | null | undefined): T | undefined;