TypeScript Narrowing
TypeScript’s type system is powerful, but sometimes the compiler needs a little help understanding your intentions. This is where type narrowing comes in. Type narrowing allows you to refine the type of a variable within a specific scope, enabling more accurate type checking and preventing runtime errors.
Let’s say you have a variable that can be either a string or a number:
let value: string | number;
value = "hello";
value = 42;
If you try to access a string-specific method on value
, TypeScript will complain, as it doesn’t know for certain that value
is a string at that particular point:
console.log(value.toUpperCase()); // Error: Property 'toUpperCase' does not exist on type 'string | number'.
Here are several ways to narrow the type:
1. Type Guards:
Type guards are functions that return a boolean indicating whether a variable is of a specific type. The typeof
and instanceof
operators are common type guards.
function isString(val: any): val is string {
return typeof val === "string";
}
if (isString(value)) {
console.log(value.toUpperCase()); // No error, value is narrowed to string
}
2. Equality Narrowing:
Checking for equality with specific values can also narrow the type.
if (value === "hello") {
console.log(value.toUpperCase()); // No error, value is narrowed to string
}
3. in
operator Narrowing:
The in
operator can check if a property exists on an object, effectively narrowing the type.
interface Dog {
bark: () => void;
}
interface Cat {
meow: () => void;
}
let animal: Dog | Cat;
if ("bark" in animal) {
animal.bark(); // No error, animal is narrowed to Dog
} else {
animal.meow(); // No error, animal is narrowed to Cat
}
4. Discriminated Unions:
Using a common property (a discriminant) in union types helps TypeScript narrow down the type based on the value of the discriminant.
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
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;
}
}
By employing these type narrowing techniques, you can write more robust and type-safe TypeScript code, leveraging the full potential of the type system. TypeScript will thank you for the extra guidance!