Type Narrowing
Similar to type widening, type narrowing is a related concept that demonstrates TypeScript’s pragmatism and usefulness. Read the code below and see if you can identify any issues.
In the above TypeScript function, you may expect the code inside the if
throw an error because " ".repeat(count: number)
expects a mumber
type as its parameter; however, the variable padding
is of a union type number|string
. How does it work? Under the hood of this seemingly uninteresting example, a lot of magic is happening. TypeScript uses specific checks for various code execution paths (if/else
, for
, in
) and makes intelligent type inference decisions. It recognises the typeof
operator as one of the type guards, and is confident that after the typeof padding === "number"
check, the variable will indeed be of number
type, allowing the code to pass the check.
const
assertions
Apart from the no-widening literal types demonstrated in the Type Widening example, TypeScript 3.4 introduced a new construct for literal values called const
assertions. It uses a type assertion in place of the type name. For example:
This feature helps TypeScript to narrow types without explicit annotations.
Note that const
assertion can only be applied immediately after a single type literal. It won’t work with expressions:
Similar to declaring a variable with const
, it only makes the variable un-reassignable and does not make it immutable.
Flow-based type inference
TypeScript uses flow-based type inference, which means TS will ‘read’ the code flow as humans would and reason the code based on flow control and operators such as if/else
, switch
, in
, typeof
||
etc., to refine the types as it walks down through the code. Flow-based type inference also exists in Flow, Kotlin, and Ceylon. It refines types within a block of code, while many static-typed languages, such as C++ and Java, use symbols to determine the types of variables.
The blow example demonstrates how TS narrows (refines) the type of the width
variable as it ‘reads’ the code:
Discriminated Union Types
Flow-based type inference can narrow simple union types effectively. However, when there are chances of overlap of properties among union type members (by definition of union, the new type can ‘pick-and-choose’ from any of properties from any of the union type members), TypeScript must check each property of the type in question to narrow it down to a known union member safely.
A common solution is to use a discriminated union types, also called sum types in other languages. You turn a regular union type into a discriminated union types by include a property of string literal tpye in all members of the union type, such as state
or type
.
References
- www.typescriptlang.org. (n.d.). Documentation - Narrowing. [online] Available at: https://www.typescriptlang.org/docs/handbook/2/narrowing.html [Accessed 2 Apr. 2023].
- www.typescriptlang.org. (n.d.). Documentation - TypeScript 3.4. [online] Available at: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions [Accessed 2 Apr. 2023].