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!
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.
In JavaScript, tuples are created using arrays. In Flow you can create tuples using the [type, type, type] syntax.
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).
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.
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.
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.
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
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With