316

This

const { foo: IFoo[] } = bar;

and this

const { foo: Array<IFoo> } = bar;

will reasonably cause an error.

And this

const { foo: TFoo } = bar;

will just destructure TFoo property.

How can types be specified for destructured object properties?

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Good question, but isn't it going to be able to infer the type from the definition of `bar` anyway? –  Sep 24 '16 at 05:34
  • The @user663031 comment should be removed as it is misleading. – Sasuke Uchiha Jun 16 '20 at 12:04
  • @SasukeUchiha The article is unavailable, but most articles can be googled by article title. It was moved to https://mariusschulz.com/blog/typing-destructured-object-parameters-in-typescript . It sheds some light indeed. – Estus Flask Jun 16 '20 at 12:10

7 Answers7

437

It turns out it's possible to specify the type after : for the whole destructuring pattern:

const {foo}: {foo: IFoo[]} = bar;

Which in reality is not any better than plain old

const foo: IFoo[] = bar.foo;
artem
  • 46,476
  • 8
  • 74
  • 78
  • 4
    But `{foo}` is not a value. It is what is usually called a "deconstructing assignment pattern". What you are seeing here is actually a special TypeScript feature which allows types of be associated with such patterns. –  Sep 24 '16 at 07:02
  • 1
    Indeed, it's more like a special case, especially compared to `let x, y, z: string` which apparently specifies type for `z` only. I updated the answer. – artem Sep 24 '16 at 07:19
  • 3
    The first one will be better if you need to do multiple destructuring. But the line would get so ugly that it might be better on separate lines. – jcollum Sep 01 '21 at 20:59
98

If you don't mind using an interface when de-structuring:

interface User {
  name: string;
  age: number;
}

const obj: any = { name: 'Johnny', age: 25 };
const { name, age }: User = obj;

The types of properties name and age should be correctly inferred to string and number respectively.

Stephen Paul
  • 37,253
  • 15
  • 92
  • 74
22

NextJS Typescript example

I had scenarios like so:

const { _id } = req.query
if (_id.substr(2)) { 
 ...
}

in which the req.query was typed like

type ParsedUrlQuery = { [key: string]: string | string[] }

so doing this worked:

const { _id } = req.query as { _id: string }
if (_id.substr(2)) { 
 ...
}

The irony of this is Typescript was correct and I should have done:

const _id = String(req.query._id) ✅ 
King Friday
  • 25,132
  • 12
  • 90
  • 84
  • how would the "correct way" work if you were destructuring multiple things? – Julix May 29 '23 at 15:49
  • 1
    `const { _id, abc, xyz } = req.query` and I think those vars would be of type `string | string[] | undefined` but I didn't verify. – King Friday May 29 '23 at 16:48
13

A follow-up to my own question.

Types don't need to be specified for object properties because they are inferred from destructured object.

Considering that bar was typed properly, foo type will be inferred:

const bar = { foo: [fooValue], ... }; // bar type is { foo: IFoo[], ... }
...
const { foo } = bar; // foo type is IFoo[]

Even if bar wasn't correctly typed (any or unknown), its type can be asserted:

const { foo } = bar as { foo: IFoo[] }; // foo type is IFoo[]
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 7
    This is true only when destructuring a typed object. If destructing something that came in as `any` then you need to type either that or the destructed variables. – Samuel Neff Aug 10 '21 at 14:36
  • 1
    @SamuelNeff True, this is what the second snippet shows. The idea here is that it's beneficial to switch from untyped to typed code asap, in this case it's done before destructuring. – Estus Flask Aug 10 '21 at 16:44
  • Completely agree; the more everything is properly typed, the better TS will pick up type info automatically, and happier devs will be. – Samuel Neff Aug 11 '21 at 14:08
6

If you want to destructor and rename

const {foo: food}: {foo: IFoo[]} = bar;

Took me a second to get the above right

Tomer
  • 89
  • 2
  • 6
1

Best way to assign types while destructure

const {foo} : {foo: IFoo[]} = bar;
0

It comes in handy when you're using multiple variables, if the function return is typed, the destructure is typed, with no need for defining a separate return type:

function getBananaDetail<detailType>(...args): { name: string, detail: detailType } {
//...
}
const { name, detail } = getBanana<number>()
toddmo
  • 20,682
  • 14
  • 97
  • 107