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.

The next generation of charts and BI.

Coming soon.

Fast. Opinionated. Collaborative. Local-first. Keyboard centric.
Crafted to the last pixel. We're looking for early alpha users.