TypeScript | 基礎文法:配列・タプル – forEachと型

TypeScript
スポンサーリンク

forEachは「配列をなめるだけ」のメソッド

forEach は、配列の各要素に対して順番に処理を実行するだけのメソッドです。
mapfilter と違って、戻り値を使って新しい配列を作ったりはしません

const nums = [1, 2, 3]; // number[]

nums.forEach(n => {
  console.log(n); // 1, 2, 3 と順番に出力
});
TypeScript

TypeScript的に言うと、forEach は「副作用(ログを出す・外の変数を書き換えるなど)を行うためのメソッド」で、
型の主役は「コールバックの引数の型」です。


forEachのコールバック引数の型推論

一番基本:value の型は「配列の要素型」

const names = ["Taro", "Hanako"]; // string[]

names.forEach(name => {
  // name: string と推論されている
  console.log(name.toUpperCase());
});
TypeScript

ここで型注釈を書いていなくても、TypeScript は
namesstring[] だから、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) => { ... });
TypeScript

TypeScriptはこれらもきちんと推論します。

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());
  }
});
TypeScript

typeofin などで型を絞り込むと、そのブロックの中では
v の型が numberstring にちゃんと変わります。
「forEach の中でも、通常の型絞り込みがそのまま効く」と覚えておくと安心です。


forEachは「戻り値を使わない」ことが型にも表れている

戻り値の型は常に void

mapfilter と違って、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
TypeScript

forEach のコールバックで何かを「return」しても、
それは完全に無視されるということが、型にもきっちり出ています。

「配列を変換したいなら map」「絞り込みたいなら filter」「ただ処理したいだけなら forEach」
この役割分担を、型が後押ししてくれているイメージです。


any[] / unknown[] と forEach の違い

any[] だと「何でもできてしまう」危険さ

const xs: any[] = [1, "2", true];

xs.forEach(x => {
  x.toFixed(2); // コンパイルは通るが、実行時に落ちる可能性
});
TypeScript

any は「型チェックを放棄する」ので、
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));
  }
});
TypeScript

unknown は「何か分からない」なので、そのままでは何もできませんが、
型ガードで絞り込めば、その中では安全に扱えます。

forEach の中でも、普段通り「型を絞ってから使う」というスタイルがそのまま通用します。


初心者がまず掴んでおきたい「forEachと型」の感覚

forEach と型の関係で、一番大事なポイントはこの3つです。

  1. 第1引数の型は「配列の要素型」から自動で決まる
  2. 第2引数(index)・第3引数(array)も、元の配列の型からちゃんと推論される
  3. 戻り値は常に void(何も返さない)なので、「変換」には使わない

だからこそ、

「配列の型をちゃんと決めておく」
「forEach の中では、その型に沿った処理だけを書く」
「何かを“作る”ときは map や reduce を使う」

という意識を持っておくと、
forEach は「ただ配列をなめるだけの、安全な道具」として気持ちよく使えるようになります。

「この処理、本当に forEach でいい? map や filter の方が型的にきれいじゃない?」
と一度立ち止まって考える癖がつくと、
配列まわりのコード全体が、ぐっと読みやすく・壊れにくくなっていきます。

タイトルとURLをコピーしました