Mapped Tuple Type
従来の
Mapped Typesに対し、
keyof T
の
T
が配列やtupleだった場合のmappingの挙動が異なる
T
が型変数の時、
keyof Tの
T
が配列やtupleだった場合の挙動が変わった
以前は
keyof unknown[] := "pop" | "push" | "concat" | "join" |..
の様にArrayのmethod名のunion型だった
v3.1以降では、
keyof unknown[] = number
の様に、 number
のみが対象になる
T
が配列の時の
keyof T
はやや特殊な扱いになる、ということを頭に入れておけば良い

今の時代に、「v3.0以前と異なる」と説明されても全く有用ではない
(比較する機会はないので)
だから、「 T
が配列以外の時と比較して特殊」と理解すればいい
とはいえ、直感に沿うようにするための変更なので、さほど深く考える必要はない
上記のPR内のコード例(一部改変)
準備
tstype Box<T> = { value: T };
type Boxified<T> = { [P in keyof T]: Box<T[P]> };
tstype T1 = Boxified<string[]>;
// Box<string>[]
type T3 = Boxified<[number, string?]>;
// [Box<number>, Box<string|undefined>?]
type T4 = Boxified<[number, ...string[]]>;
// [Box<number>, ...Box<string>[]]
type T6 = Boxified<(string | undefined)[]>;
// Box<string | undefined>[]
これ↓ は少し特殊だが、ほぼ同じ
tstype T2 = Boxified<readonly string[]>;
// readonly Box<string>[]
Box<readonly string[]>
の可能性も無きにしもあらずだが、これは配列ではない
「配列から配列へ変換される」というルールに則ったものなので理解しやすい

ts type T5 = Boxified<string[] | undefined>;
// Box<string>[] | undefined
ditributeされたあとに、mapが行われている
T
が型引数なのかどうかで結果は変わる
tstype n1 = [1, 2, 3];
type n2t<T> = { [k in keyof T]: 4 };
type n2b = n2t<n1>;
// [4, 4, 4]
type n2 = { [k in keyof n1]: 4 };
/**
* type n2 = {
* [x: number]: 4;
* 0: 4;
* 1: 4;
* 2: 4;
* length: 4;
* toString: 4;
* ... 32 more ...;
* at: 4;
* }
*/
keyof T
が、
T
が型引数で、
T
が配列/tuple
のときだけ、 length
とかを無視して number
になるよ
という話ね

genericsで無い時は、v3.0以前と同様に、 keyof 配列
は
keyof unknown[] := "pop" | "push" | "concat" | "join" |..
のように解釈されるので、上の例の様にぐちゃぐちゃの型になる
あくまでも number
になっていることに注意
だから、以下のようなコードが通ってしまう
tsconst t1: keyof n1 = 1; // ok.
const t6: keyof n1 = 6; // ok!?
[F<E> for E in T]
のような新たなsyntaxを入れて同様のことをしようというやつ
実際は、こういった新たなsyntaxは入れずに同様のことを実現している
関連する話