24

Is there a way without type hinting to create a tuple in Typescript.

If I simply do

const tuple = [1, 2];

the type of Tuple number[]

The closest I can get to a oneliner is

const tuple: [number, number] = [1, 2];

Am I missing something, or is this the only way?

Jay Wick
  • 12,325
  • 10
  • 54
  • 78
  • your solution will not work with any of indirect changes of the Array, e.g. `tuple.push(123)` – smnbbrv Apr 09 '18 at 09:25
  • @Jay tuple itself is a type , if you declare `[number, number]` then you can only push number type in the array. The purpose of declaring an array as tuple is restrict the types of known one or more array elements . – Niladri Apr 09 '18 at 09:27
  • 2
    The only way I know is with a function, this answer is the closest to a solution https://stackoverflow.com/questions/48872328/infer-tuple-type-instead-of-union-type/48872543#48872543 – Titian Cernicova-Dragomir Apr 09 '18 at 09:44
  • 2
    Wops, meant this one https://stackoverflow.com/questions/48686849/using-tuples-in-typescript-type-inference/48687313#48687313 – Titian Cernicova-Dragomir Apr 09 '18 at 09:48
  • Awesome answer there @TitianCernicova-Dragomir. Key take away is that typescript can't infer tuples :( Feel free to add this as answer here and I can accept it, given the question there is different to mine – Jay Wick Apr 09 '18 at 09:58

6 Answers6

23

With typescript 3.0, you can have your own utility function:

const tuple = <T extends any[]>(...args: T): T => args

And use it this way:

const tup = tuple(1, 2) // tup type is [number, number]
Guillaume
  • 391
  • 3
  • 7
  • This doesn't work for arrays that have already been created: https://tsplay.dev/w62avw – Shane Callanan Jul 05 '21 at 15:45
  • @ShaneCallanan The type information is already been lost after the initial array is constructed, typescript cannot magically get the array length back, which is required in tuples. The best typescript can do with the type information it know (`string[]`) is making `[...string]`, which is effectively a string array – Ferrybig Nov 11 '21 at 14:58
18

Typescript will not infer tuple types from array literals. You can specify the type explicitly, as you have, or you can create a helper function to make it a bit easier and still get some inference.

const tuple = <T extends [any] | any[]>(args: T): T => args
tuple(["A", "B"]) // [string, string]

Edit

Starting from 3.4 you can also use an as const assertion. This does have the advantage of not needing the extra function but it will generate a read-only tuple:

var t = [1, ''] as const;
t[0] = 1  //err

Starting from 3.0 you can also use tuples in rest parameter to infer tuples:

const tuple = <T extends any[]>(...args: T): T => args
tuple("A", "B") // [string, string]
N8allan
  • 2,138
  • 19
  • 32
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • 1
    thanks for the details. hopefully in future at least we can use [variadic kinds](https://github.com/Microsoft/TypeScript/issues/5453) to avoid explicitly declaring many functions – Jay Wick Apr 09 '18 at 22:39
  • 1
    @Jay I dream of variadic kinds ;) – Titian Cernicova-Dragomir Apr 10 '18 at 14:02
  • Is there a way to do `as const` assertion that return mutable tuple? Something like `[Number()] as MutableConst` which give `[number]` instead of `readonly [number]` – DrSensor Nov 26 '20 at 22:33
  • Just leaving the [link](https://github.com/microsoft/TypeScript/issues/27179#issuecomment-422606990) here, in case someone is searching for official docs of `T extends [any] | any[]`... – ford04 Apr 05 '21 at 13:19
  • `` and `` are shorter and work too. – Qwerty Apr 04 '22 at 17:18
9

As of TypeScript 3.4, you can add simply as const at the end.

const tuple = [1, 2] as const;

Full credit to @bela53's answer, which has a better example and link to TS playground.

Jay Wick
  • 12,325
  • 10
  • 54
  • 78
  • 1
    The behavior is not the expected one since it will prepend `readonly` keywords on every attribute. The other solutions are better. – 6infinity8 Feb 24 '23 at 15:35
4

TypeScript 4.0 has an additional way to implictly infer tuple types:

The type [...T], where T is an array-like type parameter, can conveniently be used to indicate a preference for inference of tuple types[:] (docs)

const tuple = <T extends unknown[]>(args: [...T]): T => args
tuple(["A", "B"]) // [string, string]

Playground

ford04
  • 66,267
  • 20
  • 199
  • 171
2

I am adding this answer for reference as I found it to be a pain that as const creates readonly tuples and that other techniques widen the types of the values

const tuple = <T extends any[]>(xs: readonly [...T]): T => xs as T;

it can be used in 2 ways:

const a = tuple(['foo', 10] as const)

a is of type ["foo", 10], is not readonly andtypeof a[number] is "foo" | 10


const b = tuple(['foo', 10]);

b is of type [string, number] and typeof b[number] is string | number

geoffrey
  • 2,080
  • 9
  • 13
0

I would recommend defining a type for the tuple because it's more expressive.

type TreeHouse = [location: Location, name: string, capacity: number];

and then use

<TreeHouse>[new Location(…), "Treeston", 6]

From what I tried this tuple literal cannot use parameter names unfortunately.

Take care of precedence! <TreeHouse>(…) has not the same precedence like new TreeHouse(…).

ChrisoLosoph
  • 459
  • 4
  • 8