Alexander Nnakwue Software Engineer. React, Node.js, Python, and other developer tools and libraries.

Real use cases for named tuples in TypeScript

5 min read 1635

Real Use Cases for Named Tuples in TypeScript

Introduction

Tuples extend the capabilities of the array data type. With tuples, we can easily construct special kinds of arrays, where elements are of fixed types with respect to an index or position. Due to the nature of TypeScript, these element types are known at the point of initialization. In essence, with tuples, we can define the type of data that can be stored in every position in an array.

Tuples are more like advanced arrays with extra features that ensure type safety, particularly in situations where we need to account for a list containing a fixed number of elements with multiple known types.

The major difference between arrays and tuples is that when we assign values to a tuple, these values must match the types defined in the tuple declaration in the same order. On the other hand, arrays can support multiple types with the any type or the bitwise OR (|) operator, but the order or structure of the elements doesn’t come into play.

In this tutorial, we are going to cover real-world use cases and applications for named tuples in TypeScript. We will get to know the importance of this data type and why it is preferred in certain cases.

At the end of the day, we will get to see firsthand how this data type contributes to the improvement of the TypeScript language in terms of allowing stricter rules as per improved documentation, maintainable code and developer productivity.

Prerequisites

Before we get started, readers should be familiar with the basics of TypeScript and types in general. To learn more about this topic, check this section of TypeScript’s documentation. Now let’s get started.

Introducing array and tuple data types

Before we begin our journey into exploring use cases for tuples in TypeScript, let’s briefly explore some simple cases where arrays can be used and how tuples can fit in perfectly well — and even better — in the same scenario.

In TS, we can declare an array of a particular data type. For example, we can declare an array of numbers by specifying the type of that element followed by square brackets: []. Let’s see how to do so:

let arr: number[];

arr = [1, 2, 3];

As we can see from the example above, in order to ensure type safety (which allows for easier annotation and documentation of our code), we need to make use of arrays, which allow for cases like this where we have lists of a particular data type. This, in fact, is the essence of a typed language like TypeScript.

For arrays with multiple data types, we can make use of the any type or the | (bitwise OR) operator. However, in this case, the order of the data is not set in stone. Let’s see an example below:

We made a custom demo for .
No really. Click here to check it out.

let arr: (string | number)[];
arr = ['Alex', 2020];
console.log(arr);

From the example above, we can decide to pass the number before the string, and it still works. The order in which we pass the data when the array is instantiated does not matter in this case, insofar as we have a combination of the types specified. This is exactly what tuples aim to solve.

With tuples, we can have a list of multiple data types whereby the order in which we pass the data type must conform to the order when the tuple was declared. In essence, the structure of the tuple needs to stay the same. Let’s see an example to better understand this concept:

let tup: [string, number];

tup = ['Alex', 19087]

In the example above, we can see that we have declared a tuple with two basic data types: string and number. Note that when we call the tup variable, we must also pass the element types in the order they are declared. Essentially, we can’t have a number at index 0 and a string at index 1, like so:

tup = [19087, 'Alex]

If we had done so, we would get the error shown below:

TSError: ⨯ Unable to compile TypeScript:
index.ts:6:8 - error TS2322: Type 'number' is not assignable to type 'string'.

6 tup = [19087, 'Alex']
         ~~~~~
index.ts:6:15 - error TS2322: Type 'string' is not assignable to type 'number'.

6 tup = [19087, 'Alex']

As we can see from the earlier examples above, we are declaring a number array and initializing it with values. This works as long as we are dealing only with element types that are numbers.

To account for arrays with multiple data types, we can make use of the any type or | operator, although in this case, the order or structure of the data is not guaranteed, which might not be what we want.

With tuples, however, we can ensure strictness with respect to the data types and the order of the data we intend to pass. Tuples allow for specifying known type boundaries around element types with a fixed number of elements.

Use cases for TypeScript tuples

Since tuples allow us to define both fixed types and order in an array, they are best when working with data that are related to each other in a sequential way (where order is important). That way, we can easily access the elements in a predetermined manner, making our desired responses predictable in behavior.

Below, we will be exploring some more use cases of tuple types in TS based on the 3.0 release, which will generally revolve around extracting and spreading parameter lists in function signatures.

1. Using tuples in rest parameters

The rest parameter syntax collects parameters into a single array variable and then expands them. With the release of TypeScript 3.0, we can now expand rest parameters with the tuple type into discrete parameters. What this means is that when a tuple type is used as a rest parameter, it gets flattened into the rest of the parameter list.

In simple terms, when a rest parameter is a tuple type, the tuple type can be expanded into a sequence of parameter lists.

Consider the example below:

declare function example(...args: [string, number]): void;

The rest parameter expands the elements of the tuple type into discrete parameters. When the function is called, args, which is represented as a rest parameter, is expanded to look exactly like the function signature below:

declare function example(args0: string, args1: number): void;

Therefore, the rest parameter syntax collects an “argument overflow” into either an array or a tuple. In summary, a tuple type forces us to pass the appropriate types to the respective function signatures. More details can be found on the TypeScript 3.0 release blog post here.

2. Spread expressions with tuples

The spread syntax expands the elements of an array or object into its element. With TypeScript 3.0, the spread operator can also expand the elements of a tuple. When a function call includes a spread expression of a tuple type as an argument, the spread expression is expanded as a sequence of arguments corresponding to the element of the tuple type.

Let’s see an example below:

type Value = [number, number];

const sample = (...value: Value) => {
  // do  something with value here
};

// create a type
let sampleTuple: Value;

sampleTuple = [20, 40];

// Passing the values as literals:
sample(20, 40);

// Passing indexes to the corresponding sampleTuple tuple
sample(sampleTuple[0], sampleTuple[1]);

// Using the spread operator to pass the full sampleTuple tuple
sample(...sampleTuple);

N.B., as we can see from the above example, we have declared a tuple type and passed it as a parameter to the function signature.

When the function is called, we can either pass the arguments as literals or via their respective indices. However, using the spread operator is a fast and clean option for passing a tuple as an argument to a function call.

Due to the nature of spread operators, the parameters are expanded as a list of arguments corresponding to the elements of the tuple type.

3. Destructure values

Because tuples are arrays under the hood, we can destructure them just like we would an array. It is important to note that the destructuring variables get the types of the corresponding tuple elements. Let us look at an example:

let tuple: [number, string, boolean];

tuple = [7, "hello", true];

let [a, b, c] = tuple; 

// a: number, b: string, c: boolean

Other use cases for TypeScript tuples

  • We can use tuples to enforce type safety when we intend to simultaneously return both response and error results from a function call
  • We can use tuples to group similar data or payloads together, e.g., a cartesian coordinate, enforcing element types in a custom csv file
  • We can also use tuples to create specific types comprised of multiple other types
  • Tuples can generally be used as parameter lists to function signatures or calls

N.B., parameter lists aren’t just ordered lists of types. To make tuples work as parameter lists, some improvements were made in the TypeScript 3.0 release. You can review them here.

Conclusion

TypeScript tuples are like arrays with a fixed number of elements. They provide us with a fixed size container that can store values of multiple types, where the order and structure are very important.

This data type is best used when we know exactly how many types we want to allow in an array. As we know, assigning an index outside of the original defined length will result in an error by the TypeScript compiler.

Note that while it is possible to modify the values of tuple elements via their indices, we must ensure to match the types provided when the tuple variable was declared. This is because we can’t alter the type or even the size of elements in the tuple once declared.

With the features we have highlighted in this post, it becomes possible to design strongly typed higher-order functions that can transform functions and their parameter lists, and in essence ensure a robust, well-documented, and maintainable codebase, which is at the very heart of why we use TypeScript.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Alexander Nnakwue Software Engineer. React, Node.js, Python, and other developer tools and libraries.

Leave a Reply