「配列mapの型推論」とは何をしてくれているのか
map は、「配列の各要素を変換して、新しい配列を作る」メソッドです。
TypeScriptは、このときに「元の配列の要素の型」と「変換関数の戻り値の型」から、新しい配列の型を自動で推論してくれます。
const nums = [1, 2, 3]; // number[]
const doubled = nums.map(n => n * 2);
// doubled の型: number[]
TypeScriptここで TypeScript は、
numsの要素はnumbermapのコールバックはnumberを受け取ってnumberを返している
と理解して、doubled を number[] と推論します。
「元の配列の要素型」と「コールバックの戻り値の型」——この2つが、map の型推論の主役です。
単純な number[] / string[] の map からイメージを掴む
number[] を number[] に変換する
const scores = [80, 90, 100]; // number[]
const plusTen = scores.map(score => score + 10);
// plusTen: number[]
TypeScriptここでの流れはこうです。
scoresの要素型はnumbermapのコールバックscore => score + 10は、score: numberを受け取ってnumberを返している- だから、
mapの結果はnumber[]
コールバックの引数 score に型を書いていなくても、
「元の配列が number[] だから、ここは number だな」と TypeScript が推論してくれます。
scores.map(score => {
// score: number と推論されている
return score.toFixed(2); // ここで string を返す
});
// 戻り値の配列の型は string[]
TypeScript「引数の型も」「戻り値の配列の型も」両方推論されているのがポイントです。
string[] を string[] に変換する
const names = ["Taro", "Hanako"]; // string[]
const upper = names.map(name => name.toUpperCase());
// upper: string[]
TypeScriptここでも同じです。
namesの要素型はstring- コールバックは
stringを受け取ってstringを返している - 結果は
string[]
「元の配列の要素型 → コールバックの引数の型 → 戻り値の配列の型」という流れが、きれいに繋がっています。
map で「型を変える」場合の推論
number[] → string[] に変換する
const nums = [1, 2, 3]; // number[]
const labels = nums.map(n => `No.${n}`);
// labels: string[]
TypeScriptここでは、
- 元の配列の要素型:
number - コールバックの戻り値の型:
string - 結果の配列の型:
string[]
というふうに、「戻り値の型」がそのまま「新しい配列の要素型」になります。
オブジェクトの配列に変換する
const ids = [1, 2, 3]; // number[]
const users = ids.map(id => ({ id, name: `user-${id}` }));
// users: { id: number; name: string }[]
TypeScriptここでは、コールバックが { id: number; name: string } というオブジェクトを返しているので、
結果の配列は「そのオブジェクトの配列」として推論されます。
const firstUser = users[0];
// firstUser: { id: number; name: string }
TypeScriptmap の中で「どんな形の値を返しているか」が、そのまま配列の型になります。
「配列の map は、“要素の型を変換する機械”」というイメージを持つと分かりやすいです。
union型配列に対する map の型推論
(number | string)[] を map する
const mixed = [1, "2", 3]; // (number | string)[]
const asString = mixed.map(v => v.toString());
// asString: string[]
TypeScriptここでは、
- 元の要素型:
number | string - コールバックの引数
vの型もnumber | string v.toString()の戻り値はstring- 結果の配列は
string[]
となります。
条件分岐で型を絞り込む場合
const lengths = mixed.map(v => {
if (typeof v === "number") {
return v * 2; // number
} else {
return v.length; // number(string の length)
}
});
// lengths: number[]
TypeScriptここでは、if の中で typeof を使って型を絞り込んでいますが、
どちらの分岐でも number を返しているので、結果の配列は number[] になります。
もし、分岐ごとに違う型を返したらどうなるか。
const mixedResult = mixed.map(v => {
if (typeof v === "number") {
return v * 2; // number
} else {
return v.toUpperCase(); // string
}
});
// mixedResult: (number | string)[]
TypeScriptこの場合、戻り値の型が number | string になるので、
結果の配列も (number | string)[] と推論されます。
map のコールバック引数の型推論(わざわざ書かなくていいところ)
引数の型は「元の配列の要素型」から自動で決まる
const nums = [1, 2, 3];
nums.map(n => {
// n: number と推論されている
return n * 2;
});
TypeScriptここで n: number とわざわざ書かなくても、
TypeScript は「nums は number[] だから、map のコールバックの引数も number だな」と推論してくれます。
同じく、インデックスや配列全体も推論されます。
nums.map((value, index, array) => {
// value: number
// index: number
// array: number[]
return value + index;
});
TypeScriptmap のコールバックは (value, index, array) の3つを受け取れますが、
それぞれの型も「元の配列の型」から自動で決まります。
map と any[] / unknown[] の危険な組み合わせ
any[] だと、map の中も型チェックが効かない
const xs: any[] = [1, "2", true];
const result = xs.map(x => x.toFixed(2)); // これも通ってしまう
TypeScriptany は「何でもアリ」なので、x.toFixed が存在するかどうかもチェックされません。
実行時に x が string や boolean だったら落ちますが、コンパイル時には分かりません。
map の型推論をちゃんと効かせたいなら、
元の配列を any[] にしないことがとても重要です。
unknown[] の場合
const xs: unknown[] = [1, "2", true];
xs.map(x => {
// x: unknown
// x.toFixed(2); // エラー:unknown にはメソッドがない
});
TypeScriptunknown は「何か分からない」なので、そのままでは何もできません。
使う前に型ガードで絞り込む必要があります。
xs.map(x => {
if (typeof x === "number") {
return x.toFixed(2); // ここでは number として扱える
}
return String(x);
});
// 戻り値の配列の型: string[]
TypeScriptunknown[] の方が any[] より安全ですが、
「ちゃんと絞り込んでから使う」という一手間が必要になります。
初心者がまず押さえておきたい「配列mapの型推論」の感覚
配列の map における型推論の本質は、たった2つです。
- 「元の配列の要素型」から、コールバックの引数の型が決まる
- 「コールバックの戻り値の型」から、新しい配列の要素型が決まる
この2つさえ掴んでおけば、
number[]→string[]に変換するnumber[]→{ id: number }[]に変換する(number | string)[]→string[]に変換する
といったパターンが、自然に読めるようになります。
そしてもうひとつ大事なのは、
「元の配列の型がきちんと決まっているほど、map の型推論は気持ちよく働く」
ということです。
any[] に逃げないunknown[] を使うなら、ちゃんと絞り込む
ユニオン型配列なら、「何が混ざるのか」を意識して設計する
こういう意識を持って map を使っていくと、
「配列を変換するたびに、型が自然についてくる」感覚が、かなりはっきり見えてきます。
