new to TypeScript, was searching for a way to define a fixed size array and came across this:
type Tuple<T, N extends number> = N extends N ? number extends N ? T[] : _TupleOf<T, N, []> : never;
type _TupleOf<T, N extends number, R extends unknown[]> = R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]>;
type Tuple9<T> = Tuple<T, 9>;
type Board9x9<P> = Tuple9<Tuple9<P>>;
...which obtains the desired result using recursive conditional types.
I was trying to understand the syntax that is being used, and I couldn't really understand what is happening behind the scenes particularly in this point: R['length'] extends N
.
I understood that it checks if the array is of the desired length and if not it'll recursively call _TupleOf
with an extra element until that condition becomes true. but why is that happening?
I searched for answers but couldn't really find anything useful.
Does R['length']
return the actual length as a number if it is applied to a type? And why extends
seems to behaves like a ==
operator in this case?
I've also tried double-checking if it behaves as i understood in this playground (you can hover over b
and c
to check the types) and it seems it does, still can't understand why tho.
Any link or explanation? thanks in advance!
CodePudding user response:
It's because:
- The type of the
length
property of a tuple is a literal numeric type equal to the number of elements in the tuple. So not just anumber
, but a specific number (like42
). - Numeric literal types extend
number
. (Hmmm... I'm not sure why I mentioned this, but it's in the examples below so I'll keep it.) - A numeric literal type only "extends" (is the same as or a subtype of) itself.
type TwoThings = [string, string];
type TwoThingsLength = TwoThings["length"];
// ^? −−− type TwoThingsLength = 2
type IsNumber<T> = T extends number ? true : false;
type A = IsNumber<27>;
// ^? −−− type A = true
type B = IsNumber<"example">;
// ^? −−− type B = false
type IsThisNumber<T, Num> = T extends Num ? true : false;
type C = IsThisNumber<27, 27>;
// ^? −−− type C = true
type D = IsThisNumber<27, 42>;
// ^? −−− type D = false
// Which leads us to
type E = IsThisNumber<TwoThingsLength, 2>; // true, they're the same literal number
// ^? −−− type E = true
type F = IsThisNumber<TwoThingsLength, 3>; // false, they aren't
// ^? −−− type F = false
So R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]>
works by:
- If
R['length']
is the same as the target lengthN
, evaluate to theR
type itself - If not, create a new tuple type which has element
T
plus all of the previous elements (...R
) — e.g., add one — and recurse