1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【初学者向け】reduce関数の動作

Posted at

【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関数は最初は難しく感じるかもしれませんが、実は:

  1. 配列を一つずつ処理
  2. 結果を蓄積変数に保存
  3. 最後に蓄積変数を返す

というシンプルな仕組みです。

特にグループ化処理では非常に威力を発揮するので、ぜひマスターしてください!

参考


この記事が皆さんのreduce関数理解の助けになれば幸いです!

質問やご指摘があれば、コメントでお気軽にお知らせください 🙌

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?