Type Guard/Narrowing
Type Narrowing and Control Flow Analysis
What is Type Narrowing
Type Guards help you narrow down types to a more specific type than initially declared.
- Telling a TS compiler a
type variable
should be understood/known as the specified type.
Narrowing asserts/ensures that the function performs the correct action based on specified type and makes your code less error prone (since it's more specific).
Beyond narrowing, the typeof
operator can be used to narrow down types based on the type of the value passed.
function padLeft(padding: number | string, input: string): string { return " ".repeat(padding) + input; } /** Argument of type 'string | number' is not assignable to parameter of type 'number'. Type 'string' is not assignable to type 'number'. */
Because the .repeat()
method exclusively takes number
as a parameter type, initially typed parameter of string | number
doesn't match the type expectation.
By narrowing the type down, you can create different behaviors within the function based on the type.
function padLeft(padding: number | string, input: string): string { // type of parameter `padding` here is type `number` via narrowing if (typeof padding === "number") { return " ".repeat(padding) + input; } // type of parameter `padding` here is type `string` return padding + input; }
Control Flow
This analysis of code based on reachability is called control flow analysis.
TypeScript uses this flow analysis to narrow types as it encounters type guards and assignments.
- When a variable is analyzed, control flow can split off and re-merge over and over again, and that variable can be observed to have a different type at each point. (See Type Predicate section)
Control flow is compile time errors
Important to note that Typescript is about compile time errors. Since Typescript is compiled to Javascript, there is no runtime type guarantee
What is Type Guard
Type guards are a function that takes a type
and returns a boolean
, telling TS that type can be narrowed down to something more specific.
- A type guard is some expression that performs a runtime check that guarantees the
type
in some scope
typeof
if (typeof a === 'string' && typeof b === 'string') { return a.concat(b); }
Returns a string
based on the type of the value passed.
"string", "number", "bigint", "boolean", "symbol", "undefined", "object", "function"
Truthiness narrowing
Using if()
, !!
, &&
, ||
, or Boolean()
, your function can guard against falsy
values
What are falsy Values: 0
, NaN
, ""
, 0n
, null
, undefined
function getUsersOnlineMessage(numUsersOnline: number) { if (numUsersOnline) { return `There are ${numUsersOnline} online now!`; } return "Nobody's here. :("; }
Equality narrowing
Using switch
, ===
, !==
, ==
, or !=
to narrow types
function example(x: string | number, y: string | boolean) { // only time below statement is true is when `x` and `y` are both `string` type if (x === y) { // We can now call 'string' type method on 'x' or 'y'. x.toUpperCase(); y.toLowerCase(); } }
in
Determines if an object or its prototype chain has a property with a given name
type Fish = { swim: () => void }; type Bird = { fly: () => void }; function move(animal: Fish | Bird) { if ("swim" in animal) { return animal.swim(); } return animal.fly(); }
instanceof
Used to check if a value is an instance of a given constructor function or class
- We can test if an object or value is derived from a class
function logValue(x: Date | string) { if (x instanceof Date) { console.log(x.toUTCString()); } else { console.log(x.toUpperCase()); } }