Reputation: 9948
Look at the example below, why:
values: [...T]
will result promiseAllTesting1
to have the type of Promise<[number, number, number]>
values: T
will result promiseAllTesting2
to have the type of Promise<number[]>
?declare function PromiseAll1<T extends any[]>(
values: [...T]
): Promise<{
[K in keyof T]: Awaited<T[K]>;
}>;
const promiseAllTesting1 = PromiseAll1([1, 2, Promise.resolve(3)]);
declare function PromiseAll2<T extends any[]>(
values: T
): Promise<{
[K in keyof T]: Awaited<T[K]>;
}>;
const promiseAllTesting2 = PromiseAll2([1, 2, Promise.resolve(3)]);
It's from this type-challenge btw
Upvotes: 1
Views: 108
Reputation: 328758
If T
is an array or tuple type, then the syntax ...T
inside a tuple type is called a variadic tuple type, as implemented in microsoft/TypeScript#39094.
Variadic tuple types allow you to concatenate tuples at the type level. So, for example, if T
is [string, number]
, then [...T, boolean]
is [string, number, boolean]
. And [...T, ...T]
is [string, number, string, number]
. And [...T]
is [string, number]
.
That means the type [...T]
is essentially the same thing as T
(although if T
is a readonly
array type or readonly
tuple type, then [...T]
will be a mutable version of it; so [...readonly [string, number]]
is [string, number]
.)
But if they're basically the same thing, what's the difference between a generic function like
const f1 = <T extends any[]>(t: T) => t;
and one like
const f2 = <T extends any[]>(t: [...T]) => t;
? The answer is that they affect generic type argument inference. The f1
version with just T
uses normal type inference in the face of arrays. Normally if you write
const a1 = [1, "two", true];
then the compiler will infer that a1
is an unordered array type, not a tuple type:
// const a1: (string | number | boolean)[]
The same thing happens when you call f1()
:
const x1 = f1([1, "two", true]);
// const x1: (string | number | boolean)[]
But for f2
with [...T]
gives the compiler a hint that you would like T
to be a tuple type, so it changes the inference rules a bit to accomplish that:
const x2 = f2([1, "two", true]);
// const x2: [number, string, boolean]
This behavior is documented in the implementing pull request linked above:
When the contextual type of an array literal is a tuple type, a tuple type is inferred for the array literal. The type
[...T]
, whereT
is an array-like type parameter, can conveniently be used to indicate a preference for inference of tuple types (emphasis added).
Therefore, if you want array literals to be inferred as unordered arrays, use just T
, but if you want them to be inferred as tuples, use [...T]
.
Upvotes: 2
Reputation: 3908
declare function PromiseAll1<T extends any[]>(
values: [...T] // (1)
): Promise<{
[K in keyof T]: Awaited<T[K]>;
}>;
(1): T
represents the elements in that array. so if values
has length=3
then T
is a type with array also length is 3 you can learn more about three dot in javascript
declare function PromiseAll2<T extends any[]>(
values: T // (2)
): Promise<{
[K in keyof T]: Awaited<T[K]>;
}>;
(2): T represents the whole array and by default an array has length
which is dynamic
Upvotes: 0