#21 TypeScriptで学ぶコレクション操作の応用(フィルタ・マップ的な考え方)の基本

はじめに

以前の記事「#7 TypeScriptで学ぶ配列の基本」では、配列の基本操作に軽く触れました。配列とは複数の値をまとめて扱うための入れ物のことで、これを「コレクション」とも呼びます。具体的には、条件に合う要素だけを残すfilter()と、各要素を加工して新しい配列にするmap()を紹介しました。今回はその発展編として、これらを実務でよく使う形で組み合わせる書き方と、TypeScriptならではの「型」が絡む注意点を見ていきます。

filter・map・reduceをチェーンして使う

実際の開発では、「絞り込み→加工→集計」という一連の流れを一度に書くことがよくあります。商品データの配列から「在庫がある商品」だけを選び、「税込価格」に変換し、その合計金額を求める例を見てみましょう。

interface Product {
  name: string;
  price: number;
  inStock: boolean;
}

const products: Product[] = [
  { name: "ノート", price: 100, inStock: true },
  { name: "ペン", price: 50, inStock: false },
  { name: "消しゴム", price: 80, inStock: true },
];

const total: number = products
  .filter((p) => p.inStock)              // 在庫があるものだけ残す
  .map((p) => p.price * 1.1)             // 税込価格(10%)に変換
  .reduce((sum, price) => sum + price, 0); // 合計を計算

console.log(total); // 198

reduce()は「複数の値をまとめて1つの値にする」ためのメソッドです。第1引数のコールバック関数(処理のたびに呼び出される関数)では、「これまでの計算結果(sum)」と「今見ている要素(price)」を受け取れます。第2引数の0は計算の初期値です。ここで注目したいのが、interface Productで型を定義しておくと、p.inStockやp.priceのようにプロパティ名を打つ際にエディタが補完候補を出してくれる点です。プレーンなJavaScriptには無い、TypeScriptならではの利点です。

型ガードでフィルタ結果の型を絞り込む

TypeScriptならではの応用として、「型ガード」を使ったフィルタリングがあります。型ガードとは、ある値が特定の型であることをコンパイラ(ここではTypeScriptをJavaScriptに変換するプログラム)に伝えるための書き方です。例えば、nullが混ざった配列からnullを取り除きたいとします。

const items: (string | null)[] = ["A", null, "B", null, "C"];

// 普通のfilterだと、戻り値の型は (string | null)[] のまま
const filtered1 = items.filter((x) => x !== null);

// 型ガードを使うと、戻り値の型が string[] に絞り込まれる
function isNotNull(x: string | null): x is string {
  return x !== null;
}
const filtered2: string[] = items.filter(isNotNull);

x is stringという戻り値の書き方が型ガード(型述語とも呼びます)です。これがあることで、TypeScriptは「この関数がtrueを返した要素はstring型に確定している」と認識します。そのため、filtered2を後で使うときにnullチェックを重ねて書かずに済みます。JavaScriptのfilter()には無い、型を持つ言語ならではの発想です。実務では、APIレスポンスの中から不要なundefinedやnullを除外して以降のコードを安全に書きたい場面でよく使われます。

for文・forEachとの使い分け

同じ集計処理をfor文で書くと、ループの添字管理や条件分岐を自分で細かく書く必要があります。一方でfilter・map・reduceは「何をしたいか」を並べる宣言的な書き方(処理の手順ではなく目的を書くスタイル)で、処理の意図が読み取りやすいのが利点です。ただし、要素数が非常に多い場合や途中で処理を打ち切りたい場合は、for文の方が効率的なこともあります。また、forEach()は戻り値が無い(常にvoidを返す)ため、コンソール出力など「新しい配列を作らない処理」に向いています。新しい配列を作りたい場合はfilter・mapを、単に繰り返し処理したいだけならforEachを、と使い分けると迷いにくくなります。

readonly配列で意図しない書き換えを防ぐ

もう1つTypeScript特有の工夫として、関数の引数にreadonly T[]という型注釈(変数や引数がどんな型かを明示する書き方)を付ける方法があります。例えばfunction sumPrices(list: readonly Product[])のように書くと、関数内でlist.push(...)のように配列を書き換えようとした時点でコンパイルエラーになります。呼び出し元の配列を安全に保ちたい場面で役立つ、型を持つ言語ならではの工夫です。

まとめ

今回は、filter・map・reduceをチェーンして使う実践的な書き方を紹介しました。あわせて、型ガードによるフィルタ結果の絞り込み、for文・forEachとの使い分け、readonly配列によるバグ防止にも触れました。TypeScriptでのコレクション操作は基本的な考え方こそJavaScriptと同じですが、型ガードやreadonly修飾子を組み合わせることで、より安全で読みやすいコードを書けるようになります。ぜひ手元のエディタで小さな配列を作り、型の恩恵を実感しながら試してみてください。

関連記事


本ページはプロモーションを含みます。

参考書籍(PR)

TypeScriptをもっと学びたい方には「プロを目指す人のためのTypeScript入門」がおすすめです。


PR

コードを書かずに自動化したい方へ

「学んだけど自分の作業に組み込む時間がない」という方向けに、ダブルクリックで動く Windows専用の自動化ツールをオーダーメイドで作成しています。気になる方は こちらをご覧ください。

コメント

このブログの人気の投稿

#25 Rustで学ぶAI API(ChatGPT等)の呼び出し方の基本

#8 C++で学ぶ文字列操作の基本

#16 TypeScriptで学ぶ辞書型(マップ)の基本