JavaScriptの基礎 - 配列の反復メソッド
概要
JavaScriptの配列は、要素を順番に処理するための反復メソッドを多数備えており、これらのメソッドはコールバック関数と組み合わせて使用する高階関数である。
反復メソッドを使用することにより、for ループを明示的に記述することなく、配列の変換・絞り込み・集約といった操作を宣言的なスタイルで表現できる。
宣言的なスタイルとは、"どのように処理するか" ではなく "何をしたいか" を記述するアプローチであり、ソースコードの可読性と保守性を向上させる。
反復メソッドには、各要素に処理を実行する forEach、要素を変換する map、条件で絞り込む filter、値に集約する reduce、
条件判定を行う some / every、変換と平坦化を同時に行う flatMap がある。
これらのメソッドは、filter() -> map() -> reduce() のようにメソッドチェーンで連結することができ、複雑なデータ変換パイプラインを簡潔に表現できる。
Reactでは map を使ったリストレンダリングが基本パターンであり、filter と組み合わせることで条件付きレンダリングも実現できる。
反復メソッドの理解は、Reactにおけるデータ駆動のUI構築に直結する。
forEach
forEach は、配列の各要素に対してコールバック関数を順番に実行するメソッドである。
戻り値は常に undefined であり、メソッドチェーンには使用できない。
基本構文を以下に示す。
arr.forEach((element, index, array) => {
// 処理
});
| 引数 | 説明 |
|---|---|
| element | 現在処理中の要素 |
| index | 現在処理中の要素のインデックス (省略可) |
| array | forEach を呼び出した配列自体 (省略可) |
使用例を以下に示す。
const fruits = ["apple", "banana", "cherry"];
// 各要素をログ出力する (副次効果の実施)
fruits.forEach((fruit, index) => {
console.log(`${index}: ${fruit}`);
});
// 0: apple
// 1: banana
// 2: cherry
// 戻り値はundefinedである
const result = fruits.forEach((fruit) => fruit.toUpperCase());
console.log(result); // undefined
forEach は break や continue を使用して途中で処理を中断することができない。
早期終了が必要な場合は for...of ループを使用することを推奨する。
const numbers = [1, 2, 3, 4, 5];
// forEachではbreakが使用できない (エラーにはならないが期待通り動作しない)
// 早期終了が必要な場合はfor...ofを使用する
for (const num of numbers) {
if (num === 3) break; // 3に達した時点で終了する
console.log(num);
}
// 1
// 2
戻り値が不要な処理 (DOMの更新、ログ出力、外部状態の変更等) に forEach を使用し、変換結果が必要な場合は map を使用することが推奨される。
map
基本的な使用方法
map は、配列の各要素にコールバック関数を適用し、変換後の要素で構成される新しい配列を返すメソッドである。
元の配列は変更されない。
基本構文を以下に示す。
const newArray = arr.map((element, index, array) => {
return 変換後の値;
});
使用例を以下に示す。
const numbers = [1, 2, 3, 4, 5];
// 各要素を2倍にする
const doubled = numbers.map((n) => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] (元の配列は変更されない)
// オブジェクトの配列から特定のプロパティを抽出する
const users = [
{ id: 1, name: "Alice", age: 25 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Carol", age: 22 }
];
const names = users.map((user) => user.name);
console.log(names); // ["Alice", "Bob", "Carol"]
// オブジェクトを変換する
const userSummaries = users.map((user) => ({
id: user.id,
label: `${user.name} (${user.age}歳)`
}));
console.log(userSummaries);
// [
// { id: 1, label: "Alice (25歳)" },
// { id: 2, label: "Bob (30歳)" },
// { id: 3, label: "Carol (22歳)" }
// ]
map の戻り値を使用しない場合は、forEach の使用を検討すること。
戻り値を無視した map の呼び出しは、不要な配列を生成するため非効率である。
Reactのリストレンダリングでの活用
Reactでは map を使用して配列からJSX要素のリストを生成することが基本パターンである。
function ProductList({ products }) {
return (
<ul>
{products.map((product) => (
<li key={product.id}>
<span>{product.name}</span>
<span>{product.price}円</span>
</li>
))}
</ul>
);
}
// 使用例
const products = [
{ id: 1, name: "りんご", price: 120 },
{ id: 2, name: "バナナ", price: 80 },
{ id: 3, name: "みかん", price: 100 }
];
// <ProductList products={products} />
各JSX要素には key プロパティを必ず指定する必要がある。
key プロパティの詳細は、JavaScriptの基礎 - 配列の反復メソッド#Reactのリストレンダリングとの関連セクションを参照すること。
filter
filter は、コールバック関数が true を返した要素のみで構成される新しい配列を返すメソッドである。
元の配列は変更されない。
条件を満たす要素が存在しない場合は、空配列 [] が返される。
基本構文を以下に示す。
const filtered = arr.filter((element, index, array) => {
return 真偽値; // trueの要素のみ新配列に含まれる
});
使用例を以下に示す。
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 偶数のみを抽出する
const evens = numbers.filter((n) => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]
// 5より大きい数のみを抽出する
const large = numbers.filter((n) => n > 5);
console.log(large); // [6, 7, 8, 9, 10]
// オブジェクトの配列をフィルタリングする
const users = [
{ id: 1, name: "Alice", active: true },
{ id: 2, name: "Bob", active: false },
{ id: 3, name: "Carol", active: true },
{ id: 4, name: "Dave", active: false }
];
const activeUsers = users.filter((user) => user.active);
console.log(activeUsers);
// [
// { id: 1, name: "Alice", active: true },
// { id: 3, name: "Carol", active: true }
// ]
// 条件を満たす要素がない場合は空配列を返す
const huge = numbers.filter((n) => n > 100);
console.log(huge); // []
filter は、メソッドチェーンで他のメソッドと連結できる。
filter の後に map を連結するパターンは特に頻繁に使用される。
const users = [
{ id: 1, name: "Alice", age: 25, active: true },
{ id: 2, name: "Bob", age: 17, active: true },
{ id: 3, name: "Carol", age: 30, active: false },
{ id: 4, name: "Dave", age: 22, active: true }
];
// アクティブかつ18歳以上のユーザの名前を取得する
const eligibleNames = users
.filter((user) => user.active && user.age >= 18)
.map((user) => user.name);
console.log(eligibleNames); // ["Alice", "Dave"]
reduce
基本的な使用方法
reduce は、配列の各要素に対してコールバック関数を累積的に適用し、最終的に単一の値を返すメソッドである。
合計値の計算、オブジェクトの生成、配列の変換等、様々な集約処理に使用できる汎用的なメソッドである。
基本構文を以下に示す。
const result = arr.reduce((accumulator, currentValue, index, array) => {
return <次のaccumulatorの値>;
}, initialValue);
| 引数 | 説明 |
|---|---|
| accumulator | 前回のコールバックの戻り値 (最初の呼び出しでは、initialValueが使用される) |
| currentValue | 現在処理中の要素 |
| index | 現在処理中のインデックス (省略可) |
| array | reduceを呼び出した配列自体 (省略可) |
基本的な使用例を以下に示す。
const numbers = [1, 2, 3, 4, 5];
// 合計値を計算する
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15
// 最大値を求める
const max = numbers.reduce((acc, n) => (n > acc ? n : acc), numbers[0]);
console.log(max); // 5
// 各要素の処理ステップ (初期値: 0)
// 処理1 : acc=0, n=1 → 0 + 1 = 1
// 処理2 : acc=1, n=2 → 1 + 2 = 3
// 処理3 : acc=3, n=3 → 3 + 3 = 6
// 処理4 : acc=6, n=4 → 6 + 4 = 10
// 処理5 : acc=10, n=5 → 10 + 5 = 15
初期値の重要性
reduce に初期値 (initialValue) を指定するかどうかで動作が変わる。
初期値を省略した場合、空配列に対して呼び出すと TypeError が発生するため、初期値を常に指定することを強く推奨する。
// 初期値ありの場合 : 空配列でも安全に動作する
const emptyArray = [];
const safeSum = emptyArray.reduce((acc, n) => acc + n, 0);
console.log(safeSum); // 0 (正常に動作する)
// 初期値なしの場合 : 空配列でTypeErrorが発生する
try {
const unsafeSum = emptyArray.reduce((acc, n) => acc + n);
}
catch (e) {
console.error(e.message);
// "Reduce of empty array with no initial value"
}
// 初期値なしの場合 : 要素が1つのみなら配列の最初の要素をそのまま返す
const single = [42];
const result = single.reduce((acc, n) => acc + n);
console.log(result); // 42
実践的な使用例
reduce は合計値の計算以外にも、様々なデータ変換に活用できる。
- カテゴリごとにグルーピングする例
const items = [ { name: "りんご", category: "果物" }, { name: "バナナ", category: "果物" }, { name: "にんじん", category: "野菜" }, { name: "みかん", category: "果物" }, { name: "たまねぎ", category: "野菜" } ]; const grouped = items.reduce((acc, item) => { const key = item.category; if (!acc[key]) { acc[key] = []; } acc[key].push(item.name); return acc; }, {}); console.log(grouped); // { // "果物": ["りんご", "バナナ", "みかん"], // "野菜": ["にんじん", "たまねぎ"] // }
- 配列を平坦化する例
const nested = [[1, 2], [3, 4], [5, 6]]; const flat = nested.reduce((acc, arr) => acc.concat(arr), []); console.log(flat); // [1, 2, 3, 4, 5, 6]
- 出現回数を集計する例
const words = ["apple", "banana", "apple", "cherry", "banana", "apple"]; const counts = words.reduce((acc, word) => { acc[word] = (acc[word] || 0) + 1; return acc; }, {}); console.log(counts); // { apple: 3, banana: 2, cherry: 1 }
reduceRight は reduce と同様だが、配列を右から左 (末尾から先頭) に向かって処理する。
入れ子構造のネストを逆順に解除する場面等で使用される。
なお、複数のステップに分けた処理は filter と map の組み合わせで記述した方が可読性が高くなる場合がある。
reduce は汎用性が高い反面、処理の意図が読み取りにくくなりやすいため、よりシンプルなメソッドで表現できる場合はそちらを優先することを推奨する。
some と every
some と every は、配列の要素が条件を満たすかどうかを真偽値で返すメソッドである。
どちらも短絡評価 (Short-circuit Evaluation) を行い、結果が確定した時点で処理を終了する。
const numbers = [1, 2, 3, 4, 5];
// some : 1つでも条件を満たす要素があればtrueを返す (OR的)
const hasEven = numbers.some((n) => n % 2 === 0);
console.log(hasEven); // true (2, 4が偶数)
const hasNegative = numbers.some((n) => n < 0);
console.log(hasNegative); // false (負の数がない)
// every: すべての要素が条件を満たすときにtrueを返す (AND的)
const allPositive = numbers.every((n) => n > 0);
console.log(allPositive); // true (全て正の数)
const allEven = numbers.every((n) => n % 2 === 0);
console.log(allEven); // false (奇数が含まれる)
短絡評価の動作を以下に示す。
const numbers = [1, 2, 3, 4, 5];
// some: trueが確定した時点で処理を終了する
// [1, 2, 3, ...] に対して「偶数か?」をチェックする
// 1 → false, 2 → true (この時点で終了)
const found = numbers.some((n) => {
console.log(`チェック中: ${n}`);
return n % 2 === 0;
});
// "チェック中: 1"
// "チェック中: 2"
// (3, 4, 5はチェックされない)
// every: falseが確定した時点で処理を終了する
const allSmall = numbers.every((n) => {
console.log(`チェック中: ${n}`);
return n < 3;
});
// "チェック中: 1"
// "チェック中: 2"
// "チェック中: 3" (3 < 3はfalse → この時点で終了)
// (4, 5はチェックされない)
下表に、some と every の空配列に対する動作を示す。
| メソッド | 空配列 [] に対する戻り値 | 理由 |
|---|---|---|
| some | false | 条件を満たす要素が1つも存在しない |
| every | true | 条件を満たさない要素が1つも存在しない (空虚な真) |
flatMap
flatMap は、map を適用した後に1段階だけ配列を平坦化するメソッドである。
arr.flatMap(fn) は arr.map(fn).flat(1) と同等だが、より効率的に実行される。
const sentences = ["hello world", "foo bar baz"];
// map + flat(1) の組み合わせ
const words1 = sentences.map((s) => s.split(" ")).flat(1);
console.log(words1); // ["hello", "world", "foo", "bar", "baz"]
// flatMapで同じ結果を得る (より効率的)
const words2 = sentences.flatMap((s) => s.split(" "));
console.log(words2); // ["hello", "world", "foo", "bar", "baz"]
flatMap は要素の追加・削除にも活用できる。
コールバックから空配列 [] を返すとその要素をスキップし、複数要素の配列を返すとそれらを展開して挿入する。
const numbers = [1, 2, 3, 4, 5];
// 偶数を除外し、奇数の前後に区切り文字を追加する
const result = numbers.flatMap((n) => {
if (n % 2 === 0) return []; // 偶数はスキップする
return ["-", n, "-"]; // 奇数は展開して追加する
});
console.log(result); // ["-", 1, "-", "-", 3, "-", "-", 5, "-"]
// 数値とその2乗を交互に並べる
const withSquares = numbers.flatMap((n) => [n, n * n]);
console.log(withSquares); // [1, 1, 2, 4, 3, 9, 4, 16, 5, 25]
flatMap が平坦化するのは1段階のみである。
深くネストした配列を完全に平坦化する場合は、flat(Infinity) を使用する。
メソッドチェーン
基本的なパターン
反復メソッドの戻り値は新しい配列 (または値) であるため、メソッドを連続して呼び出すメソッドチェーンが可能である。
ただし、forEach および reduce はチェーンの末尾に置く必要がある。
基本的なメソッドチェーンのパターンを以下に示す。
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 偶数を抽出して2乗し、その合計を求める
const result = numbers
.filter((n) => n % 2 === 0) // [2, 4, 6, 8, 10]
.map((n) => n * n) // [4, 16, 36, 64, 100]
.reduce((acc, n) => acc + n, 0); // 220
console.log(result); // 220
可読性を保つために、メソッドチェーンは各メソッドを別の行に記述するスタイルが推奨される。
実践的な例
実際のデータ処理シナリオでメソッドチェーンを活用した例を以下に示す。
- 商品リストから条件を絞り込んで表示用データを生成する例
const products = [ { id: 1, name: "ノートPC", price: 80000, category: "電子機器", stock: 10 }, { id: 2, name: "マウス", price: 3000, category: "電子機器", stock: 0 }, { id: 3, name: "デスク", price: 50000, category: "家具", stock: 5 }, { id: 4, name: "キーボード", price: 12000, category: "電子機器", stock: 8 }, { id: 5, name: "チェア", price: 35000, category: "家具", stock: 3 } ]; // 在庫ありの電子機器を価格の安い順に並べて表示用ラベルを生成する const displayItems = products .filter((p) => p.category === "電子機器" && p.stock > 0) .sort((a, b) => a.price - b.price) .map((p) => ({ id: p.id, label: `${p.name}: ${p.price.toLocaleString()}円 (在庫: ${p.stock}個)` })); console.log(displayItems); // [ // { id: 4, label: "キーボード: 12,000円 (在庫: 8個)" }, // { id: 1, label: "ノートPC: 80,000円 (在庫: 10個)" } // ]
メソッドチェーンはコードを簡潔にする一方、大規模な配列では各ステップで中間配列が生成されるため、パフォーマンスへの影響に注意が必要である。
下表に、配列の規模に応じた推奨アプローチを示す。
| 配列の規模 | 推奨アプローチ |
|---|---|
| 小規模 (数百〜数千要素程度) | メソッドチェーン版でよい (可読性を優先する) |
| 大規模 (数万要素以上) | reduce のみで1パスで処理する版が効率的 |
大規模配列に対して reduce のみで filter + map を1パスで実現する例を以下に示す。
const largeNumbers = Array.from({ length: 100000 }, (_, i) => i + 1);
// メソッドチェーン版 : 中間配列が2つ生成される
const chainResult = largeNumbers
.filter((n) => n % 2 === 0)
.map((n) => n * n);
// reduce版 : 中間配列が生成されない (1パスで処理)
const reduceResult = largeNumbers.reduce((acc, n) => {
if (n % 2 === 0) acc.push(n * n);
return acc;
}, []);
Reactのリストレンダリングとの関連
keyプロパティの役割
Reactで map を使ってリストをレンダリングする場合、各要素に key プロパティを指定する必要がある。
key は、ReactがどのDOM要素が変更・追加・削除されたかを追跡するために使用する特別なプロパティである。
key を指定しない場合は、Reactが警告を発し、リストの変更時に非効率な再レンダリングが発生する可能性がある。
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
// keyにはユニークで不変の値を使用する
<li key={todo.id}>
{todo.text}
</li>
))}
</ul>
);
}
下表に、key として使用する値の推奨度を示す。
| 値の種類 | 推奨度 | 理由 |
|---|---|---|
| データのユニークなid | 推奨 | ユニークかつ不変であるため安全 |
| 配列のインデックス | 非推奨 | リストの順序変更時にバグが発生する可能性がある |
| ランダムな値 (Math.random()等) | 禁止 | レンダリングのたびに変わり、パフォーマンスが著しく低下する |
インデックスを key に使用した場合に発生する問題を以下に示す。
// インデックスをkeyに使用した場合の問題
function BadList({ items }) {
return (
<ul>
{items.map((item, index) => (
// 問題: リストの先頭に要素を追加すると、
// 全要素のkeyがずれて非効率な再レンダリングが発生する
<li key={index}>{item.name}</li>
))}
</ul>
);
}
// 推奨: ユニークなidをkeyに使用する
function GoodList({ items }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
条件付きレンダリングとfilter
filter と map を組み合わせることにより、条件に合う要素のみをレンダリングすることができる。
function ActiveUserList({ users }) {
return (
<ul>
{users
.filter((user) => user.active)
.map((user) => (
<li key={user.id}>
{user.name}
</li>
))
}
</ul>
);
}
// 使用例
const users = [
{ id: 1, name: "Alice", active: true },
{ id: 2, name: "Bob", active: false },
{ id: 3, name: "Carol", active: true }
];
// <ActiveUserList users={users} />
// -> AliceとCarolのみがレンダリングされる
検索フィルタリングの実装例を以下に示す。
import { useState } from "react";
function SearchableList({ items }) {
const [query, setQuery] = useState("");
const filteredItems = items.filter((item) =>
item.name.toLowerCase().includes(query.toLowerCase())
);
return (
<div>
<input
type="text"
placeholder="検索..."
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<ul>
{filteredItems.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<p>{filteredItems.length}件 / {items.length}件</p>
</div>
);
}
関連情報
- JavaScriptの基礎 - 配列の操作
- 配列の作成、要素へのアクセス、追加・削除、検索メソッド
- JavaScriptの基礎 - 配列のソートと変換
- sort, reverse, flat, Array.from等のソート・変換メソッド
- JavaScriptの基礎 - コールバック関数
- コールバック関数の基本概念、高階関数、イベントリスナー