Destructuring in TypeScript


Photo of my chubby face smiling to you

Piotr Staniów • Posted on 24 Apr 20202 mins

#TypeScript

This article is a part of Frontend 101 series.

"

If you are looking for an in-depth explanation of how destructuring works in plain JavaScript, you might be interested in my previous article.

Having understood how destructuring works in JavaScript you will be or already are interested in leveraging your knowledge in TypeScript. Sometimes, it can be bewildering how you mix types, type literals and destructuring syntax, of course unless you know the rules which are clearly outlined in the following article.

Where do I put my types?

As a rule of thumb, when we want to say that a variable or argument is of given type, we follow the declaration with a colon and a type — e.g. const a: number.

The same rule will hold true also when using destructuring, we need to reflect however on where the declaration by destructuring actually occurs? In a typical example, destructuring comes in place of an identifier that would be declared in given line. The identifier is replaced with braces and properties that will be declared instead, see examples:

const object = getObject();

// { property } comes in place of object
const { property } = getObject();

function fn(argument, argument2) {}

// { a, b } comes in place of argument2:
function fn(argument, { a, b }) {}

Regardless of what features you may want to use, including aliasing, default or nested values and dynamic keys, these braces always come in place of identifier.

With that knowledge, that destructuring always takes the position of an identifier in declaration, and that type name always follows the identifier, let's try to combine these two pieces of knowledge:

interface MyType {
  a: number;
}

// Type `MyType` follows identifier `obj1` after a colon `:`
const obj1: Type = { a: 1 };
// Destructuring replaces the identifier:
const { a }: Type = { a: 2 };
// `a` is declared here with MyType['a'] type (number)

With that knowledge, the next example may seem trivial to you but just to be on the same page, let's have a look on how destructuring could be used in an arrow function and a typical function:

interface MyType { a: number; }

// Type follows the identifier
const arrowFn = (obj1: MyType): number => obj1.a;

// Destructuring replaces the identifier
const arrowFn2 = ({ a }: MyType): number => a;

function fn(obj1: MyType): number {
    return obj1.a;
}

function fn2({ a }: MyType): number {
    return a;
}

Adding fancy features

In all previous examples we've used simple destructuring syntax, without simply taking a single property out of the object as a new variable. There's nothing however preventing us from adding more features that we have learned in the previous article.

The puzzle from the previous article could be as well rewritten to TypeScript:

interface PuzzleType {
    a?: {
        d?: number;
        b: number;
    };
    b: number;
};
const c = 'd';
const { a: { [c]: b = 2 } = { b: 1 } }: PuzzleType = { b: 0 };
console.log(b); // What does it print?

You can also inspect that example in the online TypeScript playground.

The only change to the example is that we declare type for our variable adding : PuzzleType after the destructuring piece. This would also apply if we used a function.

A bit more intricate example

Adding types to destructuring becomes a bit more intricate when combined with inline type definitions using object literals. As you may already know, there's no need to declare object types using interface or type, they can also be introduced inline, as in the example below:

// Type definition follows the identifier `object` after a colon `:`
const object: { prop: number; } = { prop: 5 };

// A bit more complex example in multiple lines:
const response: {
    date: string;
    result: {
        ids: string[];
    }
} = {
    date: '2020-02-20',
    result: {
        ids: ['a934200'],
    },
};

It would probably seem scary but having read this article thus far you probably have all tools to figure out how destructuring fits into that picture. Simply, you can replace either object identifier or response identifier from the example above, with a destructuring:

// Type definition follows destructuring after a colon `:`
const { prop }: { prop: number; } = { prop: 5 };

// A bit more complex example with nested properties:
const { date, result: { ids } }: {
    date: string;
    result: {
        ids: string[];
    }
} = {
    date: '2020-02-20',
    result: {
        ids: ['a934200'],
    },
};
// `date` and `ids` are declared

It is not correct however to entangle destructuring and type definitions, even though it may seem tempting at first, and many developers beginning to use TypeScript attempt to do this. It is not possible to declare types for "parts" of object within destructuring syntax — they are always separate:

// ERROR!
const { a: { b: number } } = { a: { b: 5 } };
console.log(b);

What is more likely to happen here is that this snippet would be interpreted as if you were attempting to do aliasing: referencing b would result in throwing ReferenceError, because it's not defined. What is defined instead, is number variable, that is equal to 5.

Getting back to functions

Functions are no different to usual variable declarations we've seen in previous examples. And of course, they also don't allow mixing destructuring and types declaration 🙃

function clean({
    result: { ids, entities }
}: {
    result: {
        ids: string[];
        entities: Record<string, Entity>;
    }
}) {}

As a matter of fact, you will probably rarely seen such examples, usually it is by far more legible to simply extract the type, or to declare type of an arrow function beforehand:

const clean: (response: { timestamp: string }) => void = ({
    timestamp,
}) => {};

I hope that you have enjoyed the article and that it helps to answer questions you had been asking before you came to read it! Scroll down a little, to share your newly acquired knowledge with others on Twitter. And remember: don't mix types declarations and destructuring, and your code will be doing great. 😉

Stay connected

Stay up to date with recent articles — subscribe to the newsletter.
Unsubscribe at any time with a link in the email.

© Piotr Staniów's Blog 2023

Opinions are my own and do not represent the views of my current or past employers.