generated at
Mapped Tuple Type
従来のMapped Typesに対し、 keyof T T が配列やtupleだった場合のmappingの挙動が異なる
docs内ではMapped types on tuples and arraysなどと呼んでいる




T が型変数の時、keyof T T が配列やtupleだった場合の挙動が変わった
以前は
keyof unknown[] := "pop" | "push" | "concat" | "join" |..
の様にArrayのmethod名のunion型だった
v3.1以降では、
keyof unknown[] = number
の様に、 number のみが対象になる


T が配列の時の keyof T はやや特殊な扱いになる、ということを頭に入れておけば良いmrsekut
今の時代に、「v3.0以前と異なる」と説明されても全く有用ではない
(比較する機会はないので)
だから、「 T が配列以外の時と比較して特殊」と理解すればいい
とはいえ、直感に沿うようにするための変更なので、さほど深く考える必要はない


上記のPR内のコード例(一部改変)
準備
ts
type Box<T> = { value: T }; type Boxified<T> = { [P in keyof T]: Box<T[P]> };
これら↓は、配列から配列への型レベルmapと考えられる
ts
type 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>[]
これ↓ は少し特殊だが、ほぼ同じ
ts
type T2 = Boxified<readonly string[]>; // readonly Box<string>[]
Box<readonly string[]> の可能性も無きにしもあらずだが、これは配列ではない
「配列から配列へ変換される」というルールに則ったものなので理解しやすいmrsekut
これ↓は、Distributive Conditional Typesとの併用
ts
type T5 = Boxified<string[] | undefined>; // Box<string>[] | undefined
ditributeされたあとに、mapが行われている


T が型引数なのかどうかで結果は変わる
ts
type 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 になるよ
という話ねmrsekut
genericsで無い時は、v3.0以前と同様に、 keyof 配列
keyof unknown[] := "pop" | "push" | "concat" | "join" |..
のように解釈されるので、上の例の様にぐちゃぐちゃの型になる
あくまでも number になっていることに注意
だから、以下のようなコードが通ってしまう
ts
const t1: keyof n1 = 1; // ok. const t6: keyof n1 = 6; // ok!?





上記のPRの直前にissueが出てる
[F<E> for E in T] のような新たなsyntaxを入れて同様のことをしようというやつ
実際は、こういった新たなsyntaxは入れずに同様のことを実現している









関連する話
Mapped Typesに配列型を入れた時の挙動