In TypeScript, type narrowing is the removal of types from a union. To narrow a variable to a specific type, we use the type guard.
typeof
function padLeft(padding: number | string, input: string) {
if (typeof padding === "number") {
return " ".repeat(padding) + input;
}
return padding + input;
}
Return values:
"string""number""bigint""boolean""symbol""undefined""object"(contains array)"function"
Another check for array: Array.isArray()
“in” operator
JavaScript has an operator for determining if an object has a property with a name: the in operator.
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
return animal.swim();
}
return animal.fly();
}
instanceof
function logValue(x: Date | string) {
if (x instanceof Date) {
console.log(x.toUTCString());
(parameter) x: Date
} else {
console.log(x.toUpperCase());
(parameter) x: string
}
}
Type Predicates
To define a user-defined type guard, we simply need to define a function whose return type is a type predicate:
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
Discriminated unions
Instead of:
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}
Correct:
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
Exhaustiveness checking
never type:
When narrowing, you can reduce the options of a union to a point where you have removed all possibilities and have nothing left. In those cases, TypeScript will use a never type to represent a state which shouldn’t exist.
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
Refs:
https://www.typescriptlang.org/docs/handbook/2/narrowing.html#equality-narrowing