forEachは「配列をなめるだけ」のメソッド
forEach は、配列の各要素に対して順番に処理を実行するだけのメソッドです。map や filter と違って、戻り値を使って新しい配列を作ったりはしません。
const nums = [1, 2, 3]; // number[]
nums.forEach(n => {
console.log(n); // 1, 2, 3 と順番に出力
});
TypeScriptTypeScript的に言うと、forEach は「副作用(ログを出す・外の変数を書き換えるなど)を行うためのメソッド」で、
型の主役は「コールバックの引数の型」です。
forEachのコールバック引数の型推論
一番基本:value の型は「配列の要素型」
const names = ["Taro", "Hanako"]; // string[]
names.forEach(name => {
// name: string と推論されている
console.log(name.toUpperCase());
});
TypeScriptここで型注釈を書いていなくても、TypeScript は
「names は string[] だから、forEach のコールバックの第1引数は string だな」
と自動で推論してくれます。
同じように、number の配列ならこうなります。
const scores = [80, 90, 100]; // number[]
scores.forEach(score => {
// score: number
console.log(score + 10);
});
TypeScript「配列の要素型 → forEach の第1引数の型」という流れが、そのまま型推論に反映されています。
第2引数・第3引数の型もちゃんと付く
forEach のコールバックは、最大3つの引数を受け取れます。
array.forEach((value, index, array) => { ... });
TypeScriptTypeScriptはこれらもきちんと推論します。
const nums = [10, 20, 30]; // number[]
nums.forEach((value, index, array) => {
// value: number
// index: number
// array: number[]
console.log(index, value, array.length);
});
TypeScriptわざわざ型を書く必要はほとんどありません。
「元の配列の型さえちゃんとしていれば、forEach の引数の型も勝手に決まる」という感覚でOKです。
union型配列とforEachの型
(number | string)[] の場合
const mixed: (number | string)[] = [1, "2", 3];
mixed.forEach(v => {
// v: number | string
console.log(v);
});
TypeScriptこの場合、v の型は number | string です。
どちらの可能性もあるので、v.toFixed(2) のような「number 専用のメソッド」はそのままでは呼べません。
mixed.forEach(v => {
if (typeof v === "number") {
// ここでは v: number に絞り込まれる
console.log(v.toFixed(2));
} else {
// ここでは v: string
console.log(v.toUpperCase());
}
});
TypeScripttypeof や in などで型を絞り込むと、そのブロックの中ではv の型が number や string にちゃんと変わります。
「forEach の中でも、通常の型絞り込みがそのまま効く」と覚えておくと安心です。
forEachは「戻り値を使わない」ことが型にも表れている
戻り値の型は常に void
map や filter と違って、forEach は何も返しません。
TypeScript的には、「戻り値の型が void」です。
const result = [1, 2, 3].forEach(n => {
console.log(n);
});
// result の型: void
TypeScriptここで result を何かに使おうとすると、すぐに違和感に気づきます。
const result = [1, 2, 3].forEach(n => n * 2);
// 「n * 2」を返していても、result は void
TypeScriptforEach のコールバックで何かを「return」しても、
それは完全に無視されるということが、型にもきっちり出ています。
「配列を変換したいなら map」「絞り込みたいなら filter」「ただ処理したいだけなら forEach」
この役割分担を、型が後押ししてくれているイメージです。
any[] / unknown[] と forEach の違い
any[] だと「何でもできてしまう」危険さ
const xs: any[] = [1, "2", true];
xs.forEach(x => {
x.toFixed(2); // コンパイルは通るが、実行時に落ちる可能性
});
TypeScriptany は「型チェックを放棄する」ので、forEach の中でも「x にどんなメソッドがあるか」を一切見てくれません。
forEach の型推論をちゃんと効かせたいなら、
元の配列を any[] にしないことが本当に大事です。
unknown[] は「ちゃんと絞り込めば安全」
const xs: unknown[] = [1, "2", true];
xs.forEach(x => {
if (typeof x === "number") {
// ここでは x: number
console.log(x.toFixed(2));
}
});
TypeScriptunknown は「何か分からない」なので、そのままでは何もできませんが、
型ガードで絞り込めば、その中では安全に扱えます。
forEach の中でも、普段通り「型を絞ってから使う」というスタイルがそのまま通用します。
初心者がまず掴んでおきたい「forEachと型」の感覚
forEach と型の関係で、一番大事なポイントはこの3つです。
- 第1引数の型は「配列の要素型」から自動で決まる
- 第2引数(index)・第3引数(array)も、元の配列の型からちゃんと推論される
- 戻り値は常に void(何も返さない)なので、「変換」には使わない
だからこそ、
「配列の型をちゃんと決めておく」
「forEach の中では、その型に沿った処理だけを書く」
「何かを“作る”ときは map や reduce を使う」
という意識を持っておくと、
forEach は「ただ配列をなめるだけの、安全な道具」として気持ちよく使えるようになります。
「この処理、本当に forEach でいい? map や filter の方が型的にきれいじゃない?」
と一度立ち止まって考える癖がつくと、
配列まわりのコード全体が、ぐっと読みやすく・壊れにくくなっていきます。
