Una tupla en TypeScript es un array con un número fijo de elementos, en el que cada posición tiene su propio tipo. [string, number] significa exactamente dos elementos: primero un string y después un número. Escribes los tipos entre corchetes en el orden en que aparecen los valores.
En tiempo de ejecución una tupla es un array normal de JavaScript. Todo lo que añade una tupla (la longitud fija y el tipo de cada posición) lo comprueba el compilador y después se borra.
Sintaxis de las tuplas
| Tipo tupla | Acepta | Tipo de length |
|---|---|---|
[string, number] | exactamente un string y luego un número | 2 |
[x: number, y: number] | lo mismo, con etiquetas para leerlo mejor | 2 |
[number, number, number?] | 2 o 3 números | 2 | 3 |
[string, ...number[]] | un string y luego cualquier cantidad de números | number |
[...string[], number] | cualquier cantidad de strings y luego un número | number |
readonly [number, number] | un par que no se puede modificar | 2 |
[] | solo un array vacío | 0 |
Cada forma se explica más abajo. Fíjate en el tipo de length: en una tupla fija es un tipo literal, así que el compilador sabe que pair.length es exactamente 2.
Qué comprueba el compilador
Un tipo tupla fija el número de elementos, su orden y el tipo de cada posición. Equivocarse en cualquiera de ellos es un error de compilación:
index.ts(2,7): error TS2322: Type '[string]' is not assignable to type '[string, number]'.
Source has 1 element(s) but target requires 2.
index.ts(3,36): error TS2322: Type 'number' is not assignable to type 'string'.
index.ts(3,40): error TS2322: Type 'string' is not assignable to type 'number'.
index.ts(5,16): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'.
Un array normal nunca podría detectar el último: para string[], arr[2] es simplemente un string que en tiempo de ejecución resulta ser undefined.
Elementos con nombre
Las etiquetas documentan qué significa cada posición. No cambian nada del tipo ni de cómo se indexa, pero los editores las muestran al pasar el cursor y en las pistas de firma, lo que hace [number, number] mucho menos misterioso.
Desde TypeScript 5.2 puedes etiquetar unas posiciones y dejar otras sin etiqueta, como en [first: string, number]. Las etiquetas son solo para quien lee: [x: number, y: number] y [number, number] son el mismo tipo y se pueden asignar entre sí.
Elementos opcionales
Un ? después del tipo de un elemento hace opcional esa posición. Los elementos opcionales deben ir después de los obligatorios, y cada uno amplía el tipo de length.
Leer un elemento opcional da T | undefined, así que hace falta un valor por defecto en el patrón de desestructuración (a = 1) o una comprobación antes de hacer aritmética.
Elementos rest
Un elemento rest, ...T[], representa cualquier cantidad de elementos de tipo T. Puede ir al final, al principio o en medio, con uno como máximo por tupla.
El length de una tupla con un elemento rest es number, porque el tamaño ya no es fijo. Lo que sigue fijo es dónde están las posiciones tipadas.
Tuplas readonly y as const
readonly [T, U] elimina push, pop, splice y la asignación por índice, que es lo que debe ser un valor de longitud fija. Escribir as const después de un array literal infiere una tupla readonly de tipos literales.
(typeof SIZES)[number] convierte la tupla en una unión de los tipos de sus elementos, un patrón que se explica en indexed access types. Una tupla readonly no se puede pasar a un parámetro tipado como tupla mutable, así que las funciones que solo leen deberían aceptar readonly [number, number].
La comprobación de readonly es solo en tiempo de compilación. En tiempo de ejecución el array no está congelado (la asignación de arriba se ejecutó de verdad, como muestra la salida), así que usa Object.freeze si necesitas una garantía en tiempo de ejecución.
Devolver una tupla desde una función
Devolver varios valores como tupla es como funciona useState de React (const [value, setValue] = useState(0)). El problema: un array literal en un return se infiere como array, no como tupla.
index.ts(9,13): error TS2365: Operator '+' cannot be applied to types 'number | (() => number)' and 'number'.
index.ts(10,1): error TS2349: This expression is not callable.
Not all constituents of type 'number | (() => number)' are callable.
Type 'number' has no call signatures.
La función devuelve (number | (() => number))[], así que los dos nombres desestructurados reciben el tipo unión. Hay dos soluciones: anotar el tipo de retorno o añadir as const.
Devolver una tupla permite a quien llama nombrar las partes como quiera. Cuando hay más de dos o tres valores, o el orden no es evidente, devuelve un objeto: { count, increment } se documenta solo.
Tuplas como parámetros de función
Un parámetro rest tipado como tupla describe una lista de argumentos completa, incluidos los argumentos opcionales. Así es como el tipo utilitario integrado Parameters<T> representa los parámetros de una función.
Expandir una tupla en una llamada comprueba el tipo de cada argumento por posición, algo que no podría hacer la expansión de un (string | number)[].
Tupla frente a array
Array (string | number)[] | Tupla [string, number] | |
|---|---|---|
| Longitud | cualquiera | fija (o acotada por elementos opcionales y rest) |
Tipo de x[0] | string | number | string |
Tipo de x[5] | string | number | error de compilación TS2493 |
Tipo de length | number | 2 |
| Orden de los tipos | no se registra | se registra |
| Valor en tiempo de ejecución | array de JavaScript | el mismo array de JavaScript |
| Uso típico | listas de elementos parecidos | grupos pequeños y fijos: pares, coordenadas, [key, value], varios valores de retorno |
Las tuplas también aparecen en tipos integrados. Object.entries(obj) devuelve [string, T][], y un Map se construye a partir de tuplas [key, value]:
Una trampa: una tupla mutable sigue teniendo todos los métodos de array, así que pair.push(3) compila sobre un [string, number] y crea sin avisar un array de tres elementos cuyo tipo dice que tiene dos. Declarar las tuplas readonly cierra ese agujero. Y como los tipos se borran, los datos que vienen de fuera del programa (JSON, una API) no se comprueban contra un tipo tupla en tiempo de ejecución: valida su longitud y los tipos de sus elementos antes de fiarte de ellos.
Tuplas variádicas
Los tipos tupla pueden expandir otros tipos tupla, [...T, ...U]. Junto con los genéricos, esto permite tipar funciones que concatenan o anteponen elementos conservando cada posición:
Los tipos de las librerías también se apoyan en la inferencia de tuplas: Promise.all([fetchUser(), fetchPosts()]) se resuelve en una tupla con un tipo por cada promesa de entrada.
Preguntas frecuentes
¿Qué es una tupla en TypeScript?
Una tupla es un tipo array de longitud fija en el que cada posición tiene su propio tipo: [string, number] son exactamente dos elementos, un string y luego un número. En tiempo de ejecución es un array normal de JavaScript; la longitud y los tipos por posición solo se comprueban en tiempo de compilación.
¿Qué diferencia hay entre una tupla y un array en TypeScript?
Un tipo array como (string | number)[] tiene cualquier longitud y todos sus elementos tienen el mismo tipo (la unión), así que arr[0] es string | number. Una tupla como [string, number] tiene una longitud conocida, y t[0] es string, t[1] es number y t[2] es un error de compilación.
¿Cómo devuelvo una tupla desde una función en TypeScript?
Anota el tipo de retorno, function f(): [number, string], o termina la expresión del return con as const, que da una tupla readonly. Sin ninguna de las dos, return [count, setCount] se infiere como un array de una unión, como (number | (() => void))[], y al desestructurar obtienes tipos unión.
¿Qué son los elementos con nombre de una tupla?
Etiquetas en las posiciones, [name: string, age: number]. No cambian el tipo ni la forma de acceder (sigue siendo t[0]), pero los editores las muestran al pasar el cursor y en las pistas de parámetros de funciones cuyos parámetros se tipan como tupla. Los elementos opcionales y rest funcionan con etiquetas: [x: number, y?: number], [head: string, ...rest: number[]].
¿Se puede hacer push a una tupla en TypeScript?
En una tupla mutable, sí: push compila porque las tuplas heredan los métodos de array, aunque rompa la longitud fija. Declara la tupla readonly (o créala con as const) y push, pop y la asignación por índice pasan a ser errores de compilación.