Carl Rippon

Building SPAs

Carl Rippon
BlogBooksAbout
This site uses cookies. Click here to find out more

Strongly-typed destructuring and rest parameters

October 29, 2019
typescript

ts de re

Destructuring assignment and rest parameters are awesome and typical in codebases these days. Is it possible to strongly-type these though in TypeScript? Let’s find out.

TypeScript has tuples

Before we figure out how to strongly-type rest parameters, let’s understand tuples. A tuple can be thought of as an array with a fixed number of elements. They are nice for small and obvious data structures. For example, the useState React hook returns a tuple:

const [state, setState] = useState(initialState);

TypeScript lets us define tuples in a type annotation by specifying the type of each element in square brackets. For example:

const tomScore: [string, number]: ["Tom", 70];

Open-ended tuples

What have tuples got to do with rest parameters? Well, we’ll get there eventually.

TypeScript lets us have tuples where we can have a varying number of end elements like below:

["Tom", 70][("Jane", 70, 60)][("Fred", 70, 60, 80)];

We can specify the type for the above example as [string, ...number[]].

Strongly-typing rest parameters

I wonder if we can use an open-ended tuple to strongly-type a rest parameter? Let’s try to do this for the scores parameter in the function below:

function logScores(firstName, ...scores) {
  console.log(firstName, scores);
}

logScores("Ben", 50, 75, 85); // outputs Ben and [50, 75, 85]

Let’s try this:

function logScores(firstName: string, ...scores: [...number[]]) {
  console.log(firstName, scores);
}

If we think about it, [...number[]] is just number[]. So, this can be simplified to:

function logScores(firstName: string, ...scores: number[]) {
  console.log(firstName, scores);
}

… and if we consume the function:

logScores("Ben", 50, 75, 85); // okay
logScores("Mike", 90, 65, "65"); // Argument of type '"65"' is not assignable to parameter of type 'number'

Great - it works!

Strongly-typing destructured assignment

Specifying the type on destructured object variables is perhaps not achieved how you might first expect. The following doesn’t specify type annotations for firstName and score:

const fred = { firstName: "Fred", score: 50 };
const { firstName: string, score: number } = fred;

Instead, it specifies names for the destructured variables:

console.log(firstName); // cannot find name 'firstName'
console.log(score); // cannot find name 'score'
console.log(string); // "Fred"
console.log(number); // 50

We specify the type annotation after the destructured object as follows:

const { firstName, score }: { firstName: string; score: number } = fred;
console.log(firstName); // "Fred"
console.log(score); // 50

If we are destructuring a tuple, we specify the tuple type after the destructured tuple:

const tomScore: [string, number]: ["Tom", 70];
const [firstName, score]: [string, number]  = tomScore;
console.log(firstName);   // "Tom"
console.log(score);       // 70

We can specify a type on an open-ended tuple as follows:

const tomScores: [string, ...number[]] = ["Tom", 70, 60, 80];
const [firstName, ...scores]: [string, ...number[]] = tomScores;
console.log(firstName); // "Tom"
console.log(scores); // [70, 60, 80]

It is worth noting that often TypeScript cleverly infers the types of destructured elements, but it is good to know how to specify type annotation in the edge cases where it doesn’t.

Wrap up

TypeScript tuples are a convenient way of strongly-typing small and obvious data structures. Open-ended tuples can be used to strongly-type rest parameters. TypeScript generally smartly infers the types of destructured elements for us, but when it can’t, we can simply put a type annotation after the destructured items.


Want to learn more about React and TypeScript? Check out my book
Learn React with TypeScript 3