Chapter 02

配列メソッド

map, filter, find, reduce, forEach など React で必須の配列メソッドを PHP の array 関数と比較しながら学びます。

35 min

このチャプターで学ぶこと

  • .map() で配列を変換できる
  • .filter() で配列を絞り込める
  • .find() / .findIndex() で要素を検索できる
  • .reduce() で配列を集約できる
  • メソッドチェーンで複数の操作を組み合わせられる

配列メソッド

Reactを書くとき、配列操作は日常的な作業です。商品リストを表示する、タスクを完了済みでフィルタリングする、カートの合計金額を計算するなど、あらゆる場面で配列メソッドを使います。このチャプターではPHPの配列関数と対比しながら、JavaScriptの配列メソッドをマスターしましょう。

PHPの配列関数との比較

まず大まかな対応関係を把握しましょう。

PHPJavaScript用途
array_map().map()各要素を変換して新しい配列を作る
array_filter().filter()条件に合う要素だけを残す
in_array().includes()特定の値が配列に含まれるか確認
array_search().find() / .findIndex()条件に合う要素(またはインデックス)を探す
array_reduce().reduce()配列を単一の値に集約する
foreach.forEach()各要素に対して処理を実行する
usort().sort() / .toSorted()カスタム比較関数でソートする
count().length要素数を取得する(メソッドではなくプロパティ)

重要な違い: PHPでは array_map($callback, $array) のように関数の引数として配列を渡します。JavaScriptでは array.map(callback) のように 配列自身がメソッドを持っています。オブジェクト指向のスタイルになっているので、メソッドチェーン(後述)が自然に書けます。

.map() — 配列を変換する

.map() は配列の各要素に関数を適用して、同じ要素数の新しい配列を返します。元の配列は変更されません。

<?php
// PHP
$numbers = [1, 2, 3, 4, 5];
$doubled = array_map(fn($n) => $n * 2, $numbers);
// [2, 4, 6, 8, 10]
// JavaScript
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((n) => n * 2);
// [2, 4, 6, 8, 10]

// 元の配列は変わらない
console.log(numbers); // [1, 2, 3, 4, 5]
console.log(doubled); // [2, 4, 6, 8, 10]

オブジェクトの配列を変換する

実際の開発ではオブジェクトの配列を扱うことが多いです:

const users = [
  { id: 1, firstName: "太郎", lastName: "山田", age: 30 },
  { id: 2, firstName: "花子", lastName: "鈴木", age: 25 },
  { id: 3, firstName: "一郎", lastName: "田中", age: 35 },
];

// フルネームの配列を作る
const fullNames = users.map((user) => `${user.lastName} ${user.firstName}`);
console.log(fullNames);
// ["山田 太郎", "鈴木 花子", "田中 一郎"]

// 必要なプロパティだけを持つオブジェクトに変換(正規化)
const simplified = users.map((user) => ({
  id: user.id,
  name: `${user.lastName} ${user.firstName}`,
}));
console.log(simplified);
// [{ id: 1, name: "山田 太郎" }, ...]

// 分割代入を使うとさらに簡潔になる
const simplified2 = users.map(({ id, firstName, lastName }) => ({
  id,
  name: `${lastName} ${firstName}`,
}));

インデックスを使う

.map() のコールバックは第2引数でインデックスを受け取れます:

const fruits = ["りんご", "バナナ", "みかん"];

// インデックスを使った番号付きリスト
const numbered = fruits.map((fruit, index) => `${index + 1}. ${fruit}`);
console.log(numbered);
// ["1. りんご", "2. バナナ", "3. みかん"]
💡 map は React で最も重要なメソッドです

ReactでJSXのリストを表示するときは、ほぼ必ず .map() を使います。

function ProductList({ products }) {
  return (
    <ul>
      {products.map((product) => (
        // keyはReactが変更を効率的に追跡するために必要
        <li key={product.id}>
          {product.name}{product.price}
        </li>
      ))}
    </ul>
  );
}

.map() を使いこなせると、ReactのUIをデータから動的に生成できるようになります。

.filter() — 配列を絞り込む

.filter() は条件に合う要素だけを残した新しい配列を返します。

<?php
// PHP
$numbers = [1, 2, 3, 4, 5, 6, 7, 8];
$evens = array_filter($numbers, fn($n) => $n % 2 === 0);
// [2, 4, 6, 8]
// JavaScript
const numbers = [1, 2, 3, 4, 5, 6, 7, 8];
const evens = numbers.filter((n) => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8]

// 元の配列は変わらない
console.log(numbers); // [1, 2, 3, 4, 5, 6, 7, 8]

オブジェクトの配列に適用する

const products = [
  { id: 1, name: "Tシャツ", price: 2500, category: "衣類", inStock: true },
  { id: 2, name: "ジーンズ", price: 8000, category: "衣類", inStock: false },
  { id: 3, name: "スニーカー", price: 12000, category: "", inStock: true },
  { id: 4, name: "サンダル", price: 4000, category: "", inStock: true },
  { id: 5, name: "バッグ", price: 15000, category: "小物", inStock: false },
];

// 在庫ありの商品だけ
const inStockProducts = products.filter((p) => p.inStock);

// 5000円以下の商品だけ
const affordable = products.filter((p) => p.price <= 5000);

// 衣類カテゴリで在庫ありの商品
const availableClothes = products.filter(
  (p) => p.category === "衣類" && p.inStock
);
console.log(availableClothes);
// [{ id: 1, name: "Tシャツ", ... }]
📝 PHP の array_filter との違い

PHPの array_filter() はデフォルトでキーを保持します([0 => ..., 2 => ..., 4 => ...] のようにキーが飛ぶ)。JavaScriptの .filter() は常に0始まりの連番インデックスを持つ新しい配列を返します。

.find() / .findIndex() — 要素を検索する

.find() は条件に合う最初の要素を返します。見つからない場合は undefined を返します。

<?php
// PHP
$users = [
  ["id" => 1, "name" => "山田"],
  ["id" => 2, "name" => "鈴木"],
  ["id" => 3, "name" => "田中"],
];

// array_search は値がシンプルな配列向け
// オブジェクトの配列の場合は array_filter + array_values が一般的
$found = array_values(array_filter($users, fn($u) => $u["id"] === 2))[0] ?? null;
// JavaScript:.find() で簡潔に書ける
const users = [
  { id: 1, name: "山田" },
  { id: 2, name: "鈴木" },
  { id: 3, name: "田中" },
];

// 条件に合う最初の要素を返す
const user = users.find((u) => u.id === 2);
console.log(user); // { id: 2, name: "鈴木" }

// 見つからない場合は undefined
const notFound = users.find((u) => u.id === 99);
console.log(notFound); // undefined

// undefined チェックと組み合わせる
const targetId = 2;
const target = users.find((u) => u.id === targetId);
if (target) {
  console.log(`見つかりました:${target.name}`);
} else {
  console.log("ユーザーが見つかりません");
}

.findIndex() — インデックスを返す

.findIndex() は条件に合う要素のインデックスを返します。見つからない場合は -1 を返します:

const tasks = [
  { id: 1, title: "買い物", done: false },
  { id: 2, title: "掃除", done: true },
  { id: 3, title: "料理", done: false },
];

// タスクのインデックスを探す
const index = tasks.findIndex((t) => t.id === 2);
console.log(index); // 1

// 見つからない場合は -1
const notFound = tasks.findIndex((t) => t.id === 99);
console.log(notFound); // -1

// インデックスを使って要素を更新するパターン(Reactのstate更新でよく使う)
if (index !== -1) {
  const updatedTasks = tasks.map((task, i) =>
    i === index ? { ...task, done: true } : task
  );
}

.includes() — 値が含まれるか確認する

シンプルな値(文字列・数値)の場合は .includes() が便利です:

// PHP の in_array() に相当
const fruits = ["りんご", "バナナ", "みかん"];

console.log(fruits.includes("バナナ")); // true
console.log(fruits.includes("ぶどう")); // false

// 権限チェックなどに使える
const allowedRoles = ["admin", "editor"];
const userRole = "editor";

if (allowedRoles.includes(userRole)) {
  console.log("アクセス許可");
}

.reduce() — 配列を集約する

.reduce() は配列のすべての要素を処理して、一つの値にまとめます。最初は難しく感じるかもしれませんが、パターンがわかると強力です。

const numbers = [1, 2, 3, 4, 5];

// reduce(コールバック, 初期値)
// コールバックの引数:(累計値, 現在の要素, インデックス, 元配列)
const sum = numbers.reduce((total, n) => total + n, 0);
console.log(sum); // 15

// 動きを追ってみると:
// total=0, n=1 → 1
// total=1, n=2 → 3
// total=3, n=3 → 6
// total=6, n=4 → 10
// total=10, n=5 → 15
<?php
// PHP
$numbers = [1, 2, 3, 4, 5];
$sum = array_reduce($numbers, fn($carry, $n) => $carry + $n, 0);
echo $sum; // 15

オブジェクトの配列で使う

const orders = [
  { id: 1, product: "Tシャツ", price: 2500, quantity: 2 },
  { id: 2, product: "ジーンズ", price: 8000, quantity: 1 },
  { id: 3, product: "スニーカー", price: 12000, quantity: 1 },
];

// 合計金額を計算
const totalAmount = orders.reduce(
  (total, order) => total + order.price * order.quantity,
  0
);
console.log(totalAmount); // 25000
// (2500×2) + (8000×1) + (12000×1) = 5000 + 8000 + 12000 = 25000

// 最高価格のアイテムを見つける
const mostExpensive = orders.reduce(
  (max, order) => order.price > max.price ? order : max,
  orders[0]
);
console.log(mostExpensive.product); // "スニーカー"

配列をオブジェクト(マップ)に変換する

const users = [
  { id: 1, name: "山田太郎" },
  { id: 2, name: "鈴木花子" },
  { id: 3, name: "田中一郎" },
];

// id をキーにしたオブジェクトに変換(検索が O(1) になる)
const usersById = users.reduce((acc, user) => {
  acc[user.id] = user; // キーにidを使って格納
  return acc;
}, {});

console.log(usersById);
// { 1: { id: 1, name: "山田太郎" }, 2: ..., 3: ... }

// IDで素早く検索できる
console.log(usersById[2].name); // "鈴木花子"

カテゴリでグルーピングする

const products = [
  { name: "Tシャツ", category: "衣類" },
  { name: "ジーンズ", category: "衣類" },
  { name: "スニーカー", category: "" },
  { name: "サンダル", category: "" },
  { name: "バッグ", category: "小物" },
];

// カテゴリごとにグルーピング
const grouped = products.reduce((acc, product) => {
  const { category } = product;
  // そのカテゴリのキーがなければ空配列で初期化
  if (!acc[category]) {
    acc[category] = [];
  }
  acc[category].push(product);
  return acc;
}, {});

console.log(grouped);
// {
//   "衣類": [{ name: "Tシャツ", ... }, { name: "ジーンズ", ... }],
//   "靴":   [{ name: "スニーカー", ... }, { name: "サンダル", ... }],
//   "小物": [{ name: "バッグ", ... }]
// }
📝 reduce は強力だが読みにくくなりがち

.reduce() は何でもできる反面、複雑な処理を書くと可読性が下がります。多くのケースでは .map().filter() の組み合わせで十分です。reduce は「合計を計算する」「配列をオブジェクトに変換する」「グルーピングする」などのケースに向いています。無理に使わなくてよいです。

.forEach() / .some() / .every()

.forEach() — 副作用のある処理を実行する

const users = [
  { id: 1, name: "山田太郎", email: "yamada@example.com" },
  { id: 2, name: "鈴木花子", email: "suzuki@example.com" },
];

// 各要素に対して処理を実行する(戻り値はundefined)
users.forEach((user) => {
  console.log(`${user.name} (${user.email}) にメールを送信しました`);
});

// PHPのforeach に最も近い
// ただし新しい配列を作るなら forEach より map を使う
⚠️ forEach は値を返さない

.forEach() は常に undefined を返します。配列を変換したい場合は .map() を使ってください。

// 間違い:forEachで変換しようとしてもundefinedの配列になる
const wrong = [1, 2, 3].forEach((n) => n * 2); // undefined

// 正しい:mapを使う
const correct = [1, 2, 3].map((n) => n * 2); // [2, 4, 6]

.some() — 条件を満たす要素が存在するか

const products = [
  { name: "Tシャツ", inStock: true },
  { name: "ジーンズ", inStock: false },
  { name: "スニーカー", inStock: false },
];

// PHPの array_filter + count > 0 に相当
// 1つでも在庫ありの商品があるか
const hasAnyInStock = products.some((p) => p.inStock);
console.log(hasAnyInStock); // true(Tシャツが在庫あり)

// 実用例:カートに特定のカテゴリが含まれているか確認
const cartItems = ["りんご", "バナナ", "みかん"];
const hasBanana = cartItems.some((item) => item === "バナナ");
console.log(hasBanana); // true

.every() — すべての要素が条件を満たすか

const scores = [85, 92, 78, 95, 88];

// すべてのスコアが70点以上か
const allPassing = scores.every((score) => score >= 70);
console.log(allPassing); // true

// フォームバリデーションの例
const formFields = [
  { name: "username", value: "yamada", valid: true },
  { name: "email", value: "yamada@example.com", valid: true },
  { name: "password", value: "secret123", valid: true },
];

const isFormValid = formFields.every((field) => field.valid);
console.log(isFormValid); // true(すべてのフィールドがvalidならsubmitできる)

.sort() と .toSorted()

.sort() は配列をソートします。ただし注意が必要な点があります:

// デフォルトのsort()は文字列として比較するので数値では正しく動かない
const numbers = [10, 1, 5, 100, 21];
console.log(numbers.sort()); // [1, 10, 100, 21, 5] ← 文字列順!

// 比較関数を渡して数値ソートする
const nums = [10, 1, 5, 100, 21];
const sorted = nums.sort((a, b) => a - b); // 昇順
console.log(sorted); // [1, 5, 10, 21, 100]

// 降順
const descSorted = [10, 1, 5, 100, 21].sort((a, b) => b - a);
console.log(descSorted); // [100, 21, 10, 5, 1]

// 文字列のソート(日本語も動く)
const fruits = ["バナナ", "りんご", "みかん", "ぶどう"];
fruits.sort((a, b) => a.localeCompare(b, "ja"));
console.log(fruits); // ["バナナ", "みかん", "ぶどう", "りんご"](あいうえお順)
// オブジェクト配列を特定のプロパティでソート
const products = [
  { name: "バッグ", price: 15000 },
  { name: "Tシャツ", price: 2500 },
  { name: "スニーカー", price: 12000 },
];

// 価格の昇順
const byPrice = [...products].sort((a, b) => a.price - b.price);
console.log(byPrice.map((p) => p.name));
// ["Tシャツ", "スニーカー", "バッグ"]
⚠️ .sort() は元の配列を変更する(破壊的メソッド)

.sort() は元の配列を直接変更します(破壊的メソッド)。Reactではstateを直接変更することが禁止されているため、必ずコピーしてからソートしてください。

// NG:元の配列を直接変更してしまう
const sorted = products.sort((a, b) => a.price - b.price);

// OK:スプレッドでコピーしてからソート(元の配列を守る)
const sorted = [...products].sort((a, b) => a.price - b.price);

// OK:toSorted()(ES2023)は新しい配列を返すので安全
const sorted = products.toSorted((a, b) => a.price - b.price);

toSorted() はブラウザのサポートが十分になってきており、現代の環境では使えます。迷ったら [...arr].sort() が確実です。

メソッドチェーン

JavaScriptの配列メソッドはそれぞれ新しい配列を返すため、メソッドチェーン(複数のメソッドを . でつなぐ記法)が自然に書けます。

const products = [
  { id: 1, name: "Tシャツ", price: 2500, category: "衣類", inStock: true },
  { id: 2, name: "ジーンズ", price: 8000, category: "衣類", inStock: false },
  { id: 3, name: "スニーカー", price: 12000, category: "", inStock: true },
  { id: 4, name: "サンダル", price: 4000, category: "", inStock: true },
  { id: 5, name: "バッグ", price: 15000, category: "小物", inStock: false },
  { id: 6, name: "ベルト", price: 3000, category: "小物", inStock: true },
];

// 「在庫あり かつ 5000円未満」の商品名を価格順で取得
const affordableInStockNames = products
  .filter((p) => p.inStock)           // まず在庫ありでフィルタ
  .filter((p) => p.price < 5000)      // さらに5000円未満でフィルタ
  .sort((a, b) => a.price - b.price)  // 価格の昇順でソート
  .map((p) => p.name);                // 名前だけを取り出す

console.log(affordableInStockNames);
// ["Tシャツ", "ベルト", "サンダル"]

PHPで同じことをしようとするとこうなります:

<?php
// PHP:ネストが深くなりがち
$result = array_map(
  fn($p) => $p['name'],
  usort_result(
    array_filter($products, fn($p) => $p['inStock'] && $p['price'] < 5000),
    fn($a, $b) => $a['price'] - $b['price']
  )
);
// usort は in-place ソートなので一時変数が必要になる...

JavaScriptのメソッドチェーンは上から下に読めるので、処理の流れが直感的にわかります。

チェーンの途中を確認する

デバッグ時に .map() を一時的に挿入してログを出すテクニックがあります:

const result = products
  .filter((p) => p.inStock)
  // デバッグ用:途中の状態を確認できる
  // .map((p) => { console.log(p); return p; })
  .map((p) => p.name);

不変性(イミュータビリティ)

Reactを学ぶ前に必ず理解しておきたい概念が不変性(immutability)です。Reactではstateを直接変更してはいけません

破壊的メソッドと非破壊的メソッド

const arr = [1, 2, 3];

// ===== 元の配列を変更する(破壊的)メソッド =====
arr.push(4);      // [1, 2, 3, 4] — 元の配列を変更
arr.pop();        // [1, 2, 3]   — 末尾を削除
arr.splice(1, 1); // [1, 3]      — インデックス1から1要素を削除
arr.sort();       // 元の配列をソート
arr.reverse();    // 元の配列を逆順に

// ===== 新しい配列を返す(非破壊的)メソッド =====
const newArr = arr.map((n) => n * 2);      // 元のarrは変わらない
const filtered = arr.filter((n) => n > 1); // 元のarrは変わらない
const sliced = arr.slice(0, 2);            // 元のarrは変わらない
const concatenated = arr.concat([4, 5]);   // 元のarrは変わらない
const spread = [...arr, 4];                // スプレッドも非破壊的

Reactでの実践的なパターン

// === 要素を追加する ===
// NG(直接変更)
state.items.push(newItem);

// OK(新しい配列を作る)
const newItems = [...state.items, newItem];

// === 要素を削除する ===
// NG(直接変更)
state.items.splice(index, 1);

// OK(filterで新しい配列を作る)
const newItems = state.items.filter((item) => item.id !== targetId);

// === 要素を更新する ===
// NG(直接変更)
state.items[index].done = true;

// OK(mapで新しい配列を作る)
const newItems = state.items.map((item) =>
  item.id === targetId ? { ...item, done: true } : item
);
⚠️ Reactではstateを直接変更してはいけません

Reactはstateが変わったことを検知して画面を再描画します。しかし array.push() のように元の配列を直接変更すると、Reactは変化を検知できず、画面が更新されません。必ず新しい配列やオブジェクトを作って渡してください。

この「直接変更しない」という原則を**イミュータビリティ(不変性)**と言います。最初は面倒に感じますが、これがReactのバグを防ぐ重要なルールです。

✍ やってみよう:商品データを操作する

以下の商品データを使って、メソッドチェーンで各問題を解いてください。

const products = [
  { id: 1, name: "Tシャツ", price: 1500, category: "衣類", inStock: true },
  { id: 2, name: "ポロシャツ", price: 3500, category: "衣類", inStock: true },
  { id: 3, name: "ジーンズ", price: 8000, category: "衣類", inStock: false },
  { id: 4, name: "スニーカー", price: 9800, category: "", inStock: true },
  { id: 5, name: "ブーツ", price: 18000, category: "", inStock: false },
  { id: 6, name: "サンダル", price: 2800, category: "", inStock: true },
  { id: 7, name: "ショルダーバッグ", price: 12000, category: "バッグ", inStock: true },
  { id: 8, name: "リュック", price: 6500, category: "バッグ", inStock: true },
  { id: 9, name: "財布", price: 4200, category: "小物", inStock: false },
  { id: 10, name: "ベルト", price: 2200, category: "小物", inStock: true },
];

問題 (a): 在庫あり かつ 3000円未満の商品について、価格の昇順に並べて名前だけの配列を取得する。

期待する結果:["Tシャツ", "ベルト", "サンダル"]

問題 (b): すべての商品の合計在庫価値(price の合計)を計算する。

期待する結果:68500

問題 (c): カテゴリごとに商品数を集計したオブジェクトを作る。

期待する結果:{ "衣類": 3, "靴": 3, "バッグ": 2, "小物": 2 }


解答例:

// (a) 在庫あり・3000円未満の商品名を価格昇順で
const result_a = products
  .filter((p) => p.inStock && p.price < 3000)
  .sort((a, b) => a.price - b.price)
  .map((p) => p.name);

console.log(result_a); // ["Tシャツ", "ベルト", "サンダル"]

// (b) 全商品の合計金額
const result_b = products.reduce((total, p) => total + p.price, 0);

console.log(result_b); // 68500

// (c) カテゴリごとの商品数
const result_c = products.reduce((acc, p) => {
  // カテゴリのキーがなければ0で初期化してカウントアップ
  acc[p.category] = (acc[p.category] ?? 0) + 1;
  return acc;
}, {});

console.log(result_c); // { 衣類: 3, 靴: 3, バッグ: 2, 小物: 2 }

まとめ

このチャプターで学んだ配列メソッドをまとめます:

メソッド用途戻り値破壊的か
.map()各要素を変換新しい配列非破壊的
.filter()条件で絞り込み新しい配列非破壊的
.find()条件に合う最初の要素要素 or undefined非破壊的
.findIndex()条件に合う最初のインデックス数値(なければ-1)非破壊的
.includes()値が含まれるか確認true / false非破壊的
.reduce()配列を一つの値に集約任意の値非破壊的
.forEach()各要素に処理を実行undefined非破壊的
.some()1つでも条件を満たすかtrue / false非破壊的
.every()すべてが条件を満たすかtrue / false非破壊的
.sort()ソート元の配列破壊的
.toSorted()ソート新しい配列非破壊的

Reactでは特に .map().filter().find() を頻繁に使います。そして 元の配列を変更しない(イミュータビリティ) という原則を常に意識してください。

次のチャプターでは、JavaScriptの非同期処理(Promise と async/await)を学びます。APIへのデータ取得はReactアプリの必須機能なので、しっかり押さえていきましょう。