Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript Array of Tuples

So I have this simple example:

type SomeTuple = [string, number];

const foo = (options: SomeTuple[]) => console.log(options);

const options = [
    ['first', 1],
    ['second', 2],
];

const otherOptions: SomeTuple[] = [
    ['first', 1],
    ['second', 2],
];

foo(options); // Error

foo(otherOptions); // OK

Basically there no more much to tell, I just can't figure out why TS keeps showing me error in case where I'am not providing type for options explicitly;

Playground

Thank you for your time!

like image 969
DeltaBravo Avatar asked Feb 19 '20 11:02

DeltaBravo


People also ask

How do you create an array of tuples in TypeScript?

You can declare an array of tuple also. var employee: [number, string][]; employee = [[1, "Steve"], [2, "Bill"], [3, "Jeff"]]; TypeScript generates an array in JavaScript for the tuple variable. For example, var employee: [number, string] = [1, 'Steve'] will be compiled as var employee = [1, "Steve"] in JavaScript.

Can you make an array of tuples?

In JavaScript, tuples are created using arrays. In Flow you can create tuples using the [type, type, type] syntax.

What is difference between array and tuple in TypeScript?

The structure of the tuple needs to stay the same (a string followed by a number), whereas the array can have any combination of the two types specified (this can be extended to as many types as is required).

Does TypeScript have tuples?

TypeScript gives us a data type called tuple that helps to achieve such a purpose. It represents a heterogeneous collection of values. In other words, tuples enable storing multiple fields of different types. Tuples can also be passed as parameters to functions.


3 Answers

type TypeOfOptions = typeof options[number] // (string | number)[]
type IsTypeOfOptionsSubsetOfSomeTuple = 
  TypeOfOptions extends SomeTuple ? true : false // false is not assignable to SomeTuple

It meansTypeOfOptions is just more general type then SomeTuple and naturally cannot be used as SomeTuple, this is exactly why you do have the error. You want [string, number] you get by automatic inference (string | number)[], clearly not the same.

Why its more general because TS type inference works like that, we can change the behavior by assert our type to more specific by as or : or make value constructor which will do that.

Below few possible options how to change the initial inference:

// value constructor
const makeOptions = (...a: SomeTuple[]): SomeTuple[] => a
const options1 = makeOptions(
    ['first', 1],
    ['second', 2],
) // a is SomeTuple[]

// using :
const options2: SomeTuple[] = [
    ['first', 1],
    ['second', 2],
];

// using as
const options3 = [
    ['first', 1],
    ['second', 2],
] as SomeTuple[]

foo(options1) // ok
foo(options2) // ok
foo(options3) // ok

Additional note about question in the comment. Generally type SomeTuple is a specification of type TypeOfOptions, it means that if you have value which is TypeOfOptions it can be also SomeTuple but does not must be. Lets check if such relation is true:

type IsSomeTupleSubsetOfTypeOfOptions = SomeTuple extends TypeOfOptions ? true : false // true

So yes SomeTuple is specific variant of TypeOfOptions. It means we can assert type of TypeOfOptions into SomeTuple saying we are narrowing to this specific case. But if we do that - TS will validate the value to the narrowed type right away, if its match, type assertion is successful.

like image 88
Maciej Sikora Avatar answered Jan 17 '23 02:01

Maciej Sikora


By default ['first', 1] is an array with a string or a number, or one can say it's type is (string|number)[]. Note that an array does not specify it's length, nor does it specify which element is a string or which is a number. You can push an item with 3 or more values in it and TypeScript will be just fine with that.

In your second example (['first', 1]) it's an [string, number][]. Note that this differs from (string|number)[]. Now it knows it's an array with two elements, the first being a string, the second being a number. To be able to help figure TypeScript this out, an explicit declaration is required.

If it were to infer [string, number][] from the start, you wouldn't be able to add [123, 'foobar'], [123] or [] anymore. This would lead to more confusing errors as you would have to specify the type for each of those cases instead (fixing one issue, having other issues return). And if it would infer based on the entire file or codebase, compilation would take years for a complex program.

like image 30
Caramiriel Avatar answered Jan 17 '23 03:01

Caramiriel


TypeScript infers the type of options to be (string | number)[][] — an array of arrays containing either strings or numbers. It does not infer [string, number][].

This is a case of needing to specify the type, as in your otherOptions:

const otherOptions: SomeTuple[] = [
    ['first', 1],
    ['second', 2],
];

TypeScript inference is really good, but it doesn't handle this specific case for you. It would be too limiting on options for it to assume you won't do options.push([1, 2]) in other code.

In this case, even as const doesn't help, because then the array entries are too specific (you can't assign readonly ['first', 1] to a SomeTuple).

like image 44
T.J. Crowder Avatar answered Jan 17 '23 03:01

T.J. Crowder