Tuplas nomeadas

Você já se deparou com uma tupla no TypeScript e não conseguiu entender o que cada elemento representava? Neste artigo, vamos explorar como as tuplas nomeadas podem tornar seu código mais legível e expressivo.

O que são tuplas?

Tuplas são uma estrutura de dados parecida com arrays, mas com uma diferença fundamental: elas têm um número fixo de elementos, string[] representa um array com um número indefinido de strings.

type StringArray = string[];
 
const users: StringArray = [
    'não importa', 
    'quantas strings tem aqui', 
    'sendo string, tudo bem', 
    'o array poderia até mesmo estar vazio'
];

Já [string, string] representa uma tupla com exatamente duas strings - uma na primeira posição e outra na segunda.

type StringTuple = [string, string];
 
const users: StringTuple = [
    'aqui precisamos obrigatoriamente de duas strings',
    'nada mais, nada menos'
];

Enquanto arrays são mais permissivos e a ordem dos elementos não importa, em tuplas o comportamento é o oposto - a ordem é fixa e deve ser respeitada.

Como você definiria uma lista em que o primeiro elemento é uma string e o segundo é um número? Com arrays, você poderia escrever algo como (string | number)[] ou Array<string | number>:

type UserInfo = Array<string | number>;
 
const userInfo: UserInfo = ['alves', 21];

O problema é que essa abordagem permitiria um número indefinido de strings e numbers dentro do array, e a posição deles não seria fixa - algo como [21, '21'] seria permitido. Para esse cenário, o que você precisa é de uma tupla:

type UserInfo = [string, number];
 
const userInfo: UserInfo = ['alves', 21, 'oops'];
/* Error: type '[string, number, string]' is not 
   assignable to type 'UserInfo'.
 
   Source has 3 element(s) but target allows only 2.
/*
type UserInfo = [string, number];
 
const userInfo: UserInfo = [21, 'alves']; 
/* Error: type number is not assignable to type string.
 
   Error: type string is not assignable to type number. 
/*

O problema com tuplas anônimas

O grande problema das tuplas anônimas como [string, number] é a falta de clareza. Ao encontrar isso no código, podemos ter algumas dúvidas como:

  • O que a primeira string representa?

  • E o number? É um ID? Uma idade? Um timestamp?

Funciona para o compilador, mas nem sempre para humanos que leem o código. É aí que as tuplas nomeadas  brilham, elas trazem contexto diretamente na definição do tipo.

Tuplas nomeadas

Tuplas nomeadas (labeled/named tuples) são uma feature introduzida na versão 4.0 do TypeScript. Com elas, podemos dar nomes aos elementos das tuplas, tornando o código mais descritivo e fácil de entender.

A sintaxe é simples: basta colocar um nome antes do tipo do elemento, seguido de dois pontos:

type UserInfo = [userName: string, age: number];

Também podemos usar isso em funções que recebem um parâmetro que é um spread de uma tupla, observe nesse playground  como ficam os parâmetros da nossa função createUser sem os elementos nomeados, e como ficam com os elementos nomeados.

E caso tenha surgido a dúvida, sim, também podemos nomear elementos rest de uma tupla, talvez você imagine que a sintaxe é algo como rest: ...type[], mas na verdade a sintaxe é assim:

type UserInfo = [userName: string, age: number, ...rest: unknown[]];

Considerações finais

Um detalhe importante de se lembrar é que tuplas nomeadas tem um propósito de documentação, não muda na prática no uso de tuplas não nomeadas, além do que falei aqui.

Outro ponto importante: quando tuplas nomeadas chegaram, na versão 4.0, todos os elementos da tupla deveriam ser anônimos (não nomeados), ou nomeados, mas isso já não é mais verdade, a partir da  versão 5.2 podemos ter ambos, e você pode conferir a implementação disso  nessa pull request.

Tuplas nomeadas são uma ferramenta poderosa para melhorar a legibilidade do seu código, use-as com sabedoria e até uma próxima :)