Type Predicates and Type Narrowing
aka, The Problem of Sticky Union Types
Let's say you have a list of (string | number)[]
and want to filter out the numbers to be left with string[]
. The following intuitive sequence won't work.
const a = [1, 'two', 3, 'four']
const isString = (x): boolean => typeof x === 'string'
const b = a.filter(isString)
// b: (string | number)[] = ['two', 'four']
The reason this does not work is because the filter function only goes from same-type to same-type. It will not introspect your type and infer that it can safely augment it. Instead it will keep your union type as is.
What you likely want is to leverage a type predicate for the purpose of type narrowing. The only change in what follows is the return type of isString
was changed from boolean
to the type predicate x is string
.
const a = [1, 'two', 3, 'four']
const isString = (x): x is string => typeof x === 'string'
const b = a.filter(isString)
// b: string[] = ['two', 'four']
Another example is a (T | null)[]
where you want to remove the nulls.
const isNotNull = <T>(x: T | null): x is T => !!x
const a = [t1, null, t2].filter(isNotNull)
// a: T[] = [t1, t2]
Accessing deeply nested types
You can access deeply nested implicit types. For example, the type of a nested array:
interface A {
list: { a: string; b: number }[]
}
function fun(item: A['list'][number]) {
item.a
item.b
}
// or
type B = A['list'][number]
Also, you don't even need a type
const A = {
list: [
{ a: 'wow', b: 4 }
]
}
function fun(item: typeof A['list'][number]) {
item.a
item.b
}
type B = typeof A['list'][number]