What is TypeScript narrowing?
In TypeScript, narrowing refers to refining the type of a variable so that TypeScript understands it to be more specific than its initial type. This guide walks you through the various methods of type narrowing available in TypeScript.
typeof type guards
One of the most basic ways to narrow a type in TypeScript is using the typeof
operator. This is especially useful for distinguishing between primitive types.
let value: string | number; if (typeof value === 'string') { // value is narrowed to string here console.log(value.length); } else { // value is narrowed to number here console.log(value.toFixed(2)); }
instanceof type guards
When dealing with classes and object types, the instanceof
operator helps in narrowing.
class Dog { bark() { console.log("Woof!"); } } class Cat { meow() { console.log("Meow!"); } } let pet: Dog | Cat; if (pet instanceof Dog) { pet.bark(); // TypeScript knows pet is of type Dog here } else { pet.meow(); // TypeScript knows pet is of type Cat here }
User-defined type guards
You can also define your own type guards using a function. The return type of this function should be a type predicate, parameterName is Type
.
function isString(value: any): value is string { return typeof value === 'string'; } let item: string | number; if (isString(item)) { console.log(item.charAt(0)); // TypeScript knows item is string here } else { console.log(item.toFixed()); // TypeScript knows item is number here }
Literal type guards
When working with literal types, simple conditional checks can act as type guards.
type ButtonType = 'submit' | 'reset'; function handleClick(type: ButtonType) { if (type === 'submit') { // type is narrowed to 'submit' } else { // type is narrowed to 'reset' } }
Non-null assertion
Sometimes, you might be certain that a value is non-null or non-undefined, even though TypeScript thinks otherwise. The non-null assertion operator !
can be used in such scenarios.
let foo: string | null = getSomeString(); // hypothetical function let bar: string = foo!; // we're asserting foo is not null
Discriminated unions
A powerful way of narrowing types in TypeScript is using discriminated unions. These involve creating objects with a common literal property that TypeScript can check to narrow down the type.
interface Circle { kind: 'circle'; radius: number; } interface Square { kind: 'square'; sideLength: number; } type Shape = Circle | Square; function getArea(shape: Shape): number { if (shape.kind === 'circle') { return Math.PI * shape.radius ** 2; } else { return shape.sideLength ** 2; } }
Using in operator
The in
operator checks for the existence of a property in an object and can be used as a type guard.
type A = { foo: number }; type B = { bar: string }; function doSomething(value: A | B) { if ('foo' in value) { console.log(value.foo); // value is of type A here } else { console.log(value.bar); // value is of type B here } }
Using assert functions
Introduced in TypeScript 3.7, assertion functions allow you to define a function where the returned type is asserted to be a more specific type.
function assertIsString(val: any): asserts val is string { if (typeof val !== 'string') { throw new Error('Not a string!'); } } let data: any = fetchData(); // hypothetical function assertIsString(data); console.log(data.length); // TypeScript knows data is a string now
In conclusion, narrowing in TypeScript is a versatile tool to make the type system more expressive and safe. By leveraging the different techniques of narrowing, developers can write more robust and type-safe code.
Invite only
We're building the next generation of data visualization.
How to turn webpages into editable canvases with a JavaScript bookmarklet
Kris Lachance
How to fix the "not all code paths return a value" issue in TypeScript
Kris Lachance
Working with WebSockets in Node.js using TypeScript
Kris Lachance
Type Annotations Can Only Be Used in TypeScript Files
Kris Lachance
Guide to TypeScript Recursive Type
Kris Lachance
How to Configure Knex.js with TypeScript
Kris Lachance