【JavaScript初学者向け】reduce関数の動作を完全理解!プロジェクトグループ化を例に徹底解説
はじめに
JavaScriptを学習していると、配列のreduce
関数でつまずく方が多いのではないでしょうか?
私も最初は「なんか難しそう...」と敬遠していましたが、実は一つずつ順番に処理していくだけの単純な仕組みです。
今回は、実際のプロジェクト管理システムでよくある「プロジェクトを顧客ごとにグループ化する」処理を例に、reduce関数の動作をステップバイステップで完全解説します!
対象読者
- JavaScriptの基本は理解しているが、reduce関数が苦手な方
- 配列操作をもっと効率的に書きたい方
- 実際のコード例で学習したい方
今回解説するコード
import { prisma } from "@/lib/prisma";
export async function getGroupedProjects() {
// データベースからプロジェクトと顧客情報を取得
const projects = await prisma.project.findMany({
include: {
Customer: true,
},
});
// customerNoごとにグループ化(ここがメイン!)
const grouped = projects.reduce((acc, proj) => {
const key = proj.customerNo;
if (!acc[key]) acc[key] = [];
acc[key].push(proj);
return acc;
}, {} as Record<number, typeof projects>);
// 使いやすい形式に変換して返す
return Object.entries(grouped).map(([customerNo, projects]) => ({
customerNo: Number(customerNo),
customerName: projects[0].Customer.customerName,
projects,
}));
}
この記事では、特にreduce部分を重点的に解説していきます!
reduce関数とは?
reduce関数は、配列の各要素を一つずつ処理して、最終的に一つの値にまとめる関数です。
配列.reduce((蓄積変数, 現在の要素) => {
// 処理内容
return 更新された蓄積変数;
}, 初期値);
実際のデータで動作を見てみよう
サンプルデータ
以下のようなプロジェクト配列があるとします:
const projects = [
{ id: 1, name: "Webサイト制作", customerNo: 100 },
{ id: 2, name: "システム開発", customerNo: 200 },
{ id: 3, name: "アプリ開発", customerNo: 100 },
{ id: 4, name: "保守運用", customerNo: 200 },
{ id: 5, name: "デザイン", customerNo: 300 }
];
目標
これを以下のように顧客番号ごとにグループ化したい:
{
100: [Webサイト制作, アプリ開発],
200: [システム開発, 保守運用],
300: [デザイン]
}
ステップバイステップ解説
ステップ1: 最初の要素を処理
// 処理する要素
proj = { id: 1, name: "Webサイト制作", customerNo: 100 }
// 現在のacc(初期値)
acc = {}
// 処理内容
const key = proj.customerNo; // key = 100
if (!acc[key]) acc[key] = []; // acc[100]を作成
acc[key].push(proj); // プロジェクトを追加
// 結果
acc = {
100: [{ id: 1, name: "Webサイト制作", customerNo: 100 }]
}
ステップ2: 2番目の要素を処理
// 処理する要素
proj = { id: 2, name: "システム開発", customerNo: 200 }
// 現在のacc
acc = {
100: [{ id: 1, name: "Webサイト制作", customerNo: 100 }]
}
// 処理内容
const key = proj.customerNo; // key = 200
if (!acc[key]) acc[key] = []; // acc[200]を新規作成
acc[key].push(proj); // プロジェクトを追加
// 結果
acc = {
100: [{ id: 1, name: "Webサイト制作", customerNo: 100 }],
200: [{ id: 2, name: "システム開発", customerNo: 200 }]
}
ステップ3: 3番目の要素を処理(重要!)
// 処理する要素
proj = { id: 3, name: "アプリ開発", customerNo: 100 }
// 現在のacc
acc = {
100: [{ id: 1, name: "Webサイト制作", customerNo: 100 }],
200: [{ id: 2, name: "システム開発", customerNo: 200 }]
}
// 処理内容
const key = proj.customerNo; // key = 100
if (!acc[key]) acc[key] = []; // acc[100]は既に存在するので何もしない
acc[key].push(proj); // 既存の配列にプロジェクトを追加
// 結果
acc = {
100: [
{ id: 1, name: "Webサイト制作", customerNo: 100 },
{ id: 3, name: "アプリ開発", customerNo: 100 } // ← 追加された!
],
200: [{ id: 2, name: "システム開発", customerNo: 200 }]
}
ポイント: 同じ顧客番号の場合は、既存の配列に追加されます!
ステップ4: 4番目の要素を処理
// 処理する要素
proj = { id: 4, name: "保守運用", customerNo: 200 }
// 処理後の結果
acc = {
100: [
{ id: 1, name: "Webサイト制作", customerNo: 100 },
{ id: 3, name: "アプリ開発", customerNo: 100 }
],
200: [
{ id: 2, name: "システム開発", customerNo: 200 },
{ id: 4, name: "保守運用", customerNo: 200 } // ← 追加された!
]
}
ステップ5: 最後の要素を処理
// 処理する要素
proj = { id: 5, name: "デザイン", customerNo: 300 }
// 最終結果
acc = {
100: [
{ id: 1, name: "Webサイト制作", customerNo: 100 },
{ id: 3, name: "アプリ開発", customerNo: 100 }
],
200: [
{ id: 2, name: "システム開発", customerNo: 200 },
{ id: 4, name: "保守運用", customerNo: 200 }
],
300: [
{ id: 5, name: "デザイン", customerNo: 300 } // ← 新規グループ作成
]
}
🧠 理解のポイント
1. accは「蓄積器」の役割
- 毎回の処理結果が蓄積されていく
- 初期値は空のオブジェクト
{}
- 各ステップで少しずつ内容が増えていく
2. 条件分岐が重要
if (!acc[key]) acc[key] = [];
この行により:
- 新しい顧客番号: 新しい配列を作成
- 既存の顧客番号: 既存の配列を使用
3. グループ化の仕組み
同じcustomerNo
を持つプロジェクトが同じ配列に集められる:
-
customerNo: 100
→ 2つのプロジェクト -
customerNo: 200
→ 2つのプロジェクト -
customerNo: 300
→ 1つのプロジェクト
実用的な応用例
商品をカテゴリ別にグループ化
const products = [
{ name: "iPhone", category: "スマホ" },
{ name: "iPad", category: "タブレット" },
{ name: "Android", category: "スマホ" }
];
const groupedByCategory = products.reduce((acc, product) => {
const key = product.category;
if (!acc[key]) acc[key] = [];
acc[key].push(product);
return acc;
}, {});
// 結果
// {
// "スマホ": [iPhone, Android],
// "タブレット": [iPad]
// }
売上を月別に集計
const sales = [
{ amount: 1000, month: "2024-01" },
{ amount: 2000, month: "2024-01" },
{ amount: 1500, month: "2024-02" }
];
const monthlyTotal = sales.reduce((acc, sale) => {
const key = sale.month;
if (!acc[key]) acc[key] = 0;
acc[key] += sale.amount;
return acc;
}, {});
// 結果
// {
// "2024-01": 3000,
// "2024-02": 1500
// }
よくあるつまずきポイント
1. 初期値を忘れる
// ❌ 初期値なし(エラーになる可能性)
array.reduce((acc, item) => { ... });
// ✅ 初期値を指定
array.reduce((acc, item) => { ... }, {});
2. returnを忘れる
// ❌ returnがない
array.reduce((acc, item) => {
acc[item.key] = item.value;
// return acc; ← これがないとundefinedが返される
}, {});
// ✅ accを返す
array.reduce((acc, item) => {
acc[item.key] = item.value;
return acc; // ← 必須!
}, {});
3. 配列の初期化を忘れる
// ❌ 配列を初期化しない
if (!acc[key]) {
// 初期化を忘れると、pushでエラー
}
acc[key].push(item);
// ✅ 配列を初期化
if (!acc[key]) acc[key] = [];
acc[key].push(item);
まとめ
reduce関数は最初は難しく感じるかもしれませんが、実は:
- 配列を一つずつ処理
- 結果を蓄積変数に保存
- 最後に蓄積変数を返す
というシンプルな仕組みです。
特にグループ化処理では非常に威力を発揮するので、ぜひマスターしてください!
参考
この記事が皆さんのreduce関数理解の助けになれば幸いです!
質問やご指摘があれば、コメントでお気軽にお知らせください 🙌