そもそも reduce って何をする関数か
Array.prototype.reduce は、「配列を 1 つの値に“畳み込む”ための関数」です。
合計を出したり、オブジェクトに集計したり、別の配列に変形したり――「最終的に 1 つの結果を作る」ときに使います。
ざっくりいうと、
array.reduce((acc, item) => {
// acc に「これまでの結果」
// item に「今見ている要素」
// 新しい acc を返す
}, 初期値);
JavaScriptという形で、「acc(蓄積値)」を更新しながら、配列を頭から順に処理していくイメージです。
reduce が難しく感じる理由と、ヘルパーを作る意味
初心者が reduce を難しく感じるポイントはだいたいここです。
- acc(蓄積値)が何なのか分かりにくい
- 初期値を何にすればいいか迷う
- 1 行に詰め込みすぎて、何をしているのか読めない
業務では、reduce を使う場面が多いのに、毎回「acc って何だっけ?」から始まるとしんどい。
そこで、「よくあるパターンを reduce ヘルパーとして切り出しておく」と、コードが一気に読みやすくなります。
合計を出すヘルパー:sumBy
実装と考え方
「配列の数値を合計したい」「オブジェクト配列の特定プロパティの合計を出したい」――これは reduce の超定番です。
function sumBy(array, selector) {
if (!Array.isArray(array)) {
return 0;
}
if (typeof selector !== "function") {
selector = (x) => x;
}
return array.reduce((acc, item) => {
const value = selector(item);
const num = typeof value === "number" ? value : 0;
return acc + num;
}, 0);
}
JavaScript重要なポイントをかみ砕きます。
selectorは「item から“足したい値”を取り出す関数」- selector が渡されなければ「そのまま値を使う」
- 値が数値でなければ 0 とみなして安全側に倒す
- 初期値は
0(合計のスタート地点)
例題:売上合計を出す
const sales = [
{ id: 1, amount: 1000 },
{ id: 2, amount: 2500 },
{ id: 3, amount: 1500 },
];
const total = sumBy(sales, (s) => s.amount);
// 5000
JavaScript「売上配列から合計金額を出す」という業務でありがちな処理が、sumBy で一行になります。
集計ヘルパー:groupCount(件数集計)
実装と考え方
「ステータスごとの件数」「カテゴリごとの件数」など、「グループごとに何件あるか」を集計するのも reduce の定番です。
function groupCount(array, keySelector) {
if (!Array.isArray(array)) {
return {};
}
if (typeof keySelector !== "function") {
keySelector = (x) => String(x);
}
return array.reduce((acc, item) => {
const key = keySelector(item);
if (!Object.prototype.hasOwnProperty.call(acc, key)) {
acc[key] = 0;
}
acc[key] += 1;
return acc;
}, {});
}
JavaScriptここでの重要ポイントは、
keySelectorが「どのキーで集計するか」を決める- acc は「集計結果のオブジェクト」
- 初期値は
{}(空の集計表) - キーがまだなければ 0 からスタートして、1 件ずつ足していく
例題:ステータスごとの件数を集計する
const tasks = [
{ id: 1, status: "todo" },
{ id: 2, status: "doing" },
{ id: 3, status: "done" },
{ id: 4, status: "doing" },
];
const counts = groupCount(tasks, (t) => t.status);
/*
{
todo: 1,
doing: 2,
done: 1
}
*/
JavaScript「ステータスごとの件数」を reduce で書くとゴチャつきがちですが、groupCount にすると「何をしたいか」が一目で分かります。
グループ化ヘルパー:groupBy(配列を“分類”する)
実装と考え方
「カテゴリごとに配列を分けたい」「日付ごとにログをまとめたい」――これは 「グループ化」です。
function groupBy(array, keySelector) {
if (!Array.isArray(array)) {
return {};
}
if (typeof keySelector !== "function") {
keySelector = (x) => String(x);
}
return array.reduce((acc, item) => {
const key = keySelector(item);
if (!Object.prototype.hasOwnProperty.call(acc, key)) {
acc[key] = [];
}
acc[key].push(item);
return acc;
}, {});
}
JavaScriptポイントは、
- acc は「グループごとの配列を持つオブジェクト」
- 初期値は
{} - キーがまだなければ
[]からスタート - 各グループの配列に
pushしていく
例題:ステータスごとにタスクをグループ化する
const tasks = [
{ id: 1, status: "todo" },
{ id: 2, status: "doing" },
{ id: 3, status: "done" },
{ id: 4, status: "doing" },
];
const grouped = groupBy(tasks, (t) => t.status);
/*
{
todo: [{ id: 1, status: "todo" }],
doing: [{ id: 2, status: "doing" }, { id: 4, status: "doing" }],
done: [{ id: 3, status: "done" }]
}
*/
JavaScript「ステータスごとにタスクをまとめたい」という業務でよくある処理が、groupBy でかなり読みやすく書けます。
reduce ヘルパーで一番大事な“acc のイメージ”
reduce を使うとき、acc(蓄積値)が何なのかを最初に決めるのが超重要です。
- 合計なら acc は「数値」、初期値は
0 - 件数集計なら acc は「オブジェクト」、初期値は
{} - グループ化なら acc は「オブジェクト(キーごとに配列)」、初期値は
{}
そして、「1 要素処理するたびに acc をどう更新するか」を、
“日本語で説明できるレベル”まで分解してからコードにするのがコツです。
例えば groupCount なら、
- acc は「集計表」
- 要素を 1 つ見る
- キーを決める
- そのキーのカウンタを 1 増やす
というストーリーが頭にあると、reduce がただの「難しい関数」ではなく、
「ストーリーをコードにしたもの」に見えてきます。
手を動かして reduce ヘルパーの感覚をつかむ
次のコードをコンソールで実行して、挙動を自分の目で確認してみてください。
function sumBy(array, selector) {
if (!Array.isArray(array)) return 0;
if (typeof selector !== "function") selector = (x) => x;
return array.reduce((acc, item) => {
const value = selector(item);
const num = typeof value === "number" ? value : 0;
return acc + num;
}, 0);
}
function groupCount(array, keySelector) {
if (!Array.isArray(array)) return {};
if (typeof keySelector !== "function") keySelector = (x) => String(x);
return array.reduce((acc, item) => {
const key = keySelector(item);
if (!Object.prototype.hasOwnProperty.call(acc, key)) {
acc[key] = 0;
}
acc[key] += 1;
return acc;
}, {});
}
function groupBy(array, keySelector) {
if (!Array.isArray(array)) return {};
if (typeof keySelector !== "function") keySelector = (x) => String(x);
return array.reduce((acc, item) => {
const key = keySelector(item);
if (!Object.prototype.hasOwnProperty.call(acc, key)) {
acc[key] = [];
}
acc[key].push(item);
return acc;
}, {});
}
const sales = [
{ id: 1, amount: 1000 },
{ id: 2, amount: 2500 },
{ id: 3, amount: 1500 },
];
console.log(sumBy(sales, (s) => s.amount)); // 5000
const tasks = [
{ id: 1, status: "todo" },
{ id: 2, status: "doing" },
{ id: 3, status: "done" },
{ id: 4, status: "doing" },
];
console.log(groupCount(tasks, (t) => t.status));
console.log(groupBy(tasks, (t) => t.status));
JavaScript「acc がどう育っていくか」「初期値がどう効いているか」を意識しながら眺めてみると、
reduce がだいぶ“怖くないもの”になってくるはずです。
まとめ:reduce ヘルパーで「よくある集計」を名前付きにする
業務コードでは、「合計」「件数集計」「グループ化」みたいな reduce パターンが何度も出てきます。
そのたびに生の reduce を書くのではなく、
export function sumBy(...) { ... }
export function groupCount(...) { ... }
export function groupBy(...) { ... }
JavaScriptのようなヘルパーをプロジェクトに置いておく。
そして、「集計したくなったらまずヘルパーを探す」と決めておく。
そうすると、
- 何をしたいコードなのかが関数名で分かる
- reduce の“お作法”を毎回思い出さなくて済む
- 初心者でも「sumBy なら合計」「groupBy ならグループ化」と直感的に使える
という状態に近づいていきます。
reduce 自体は強力で抽象的な道具なので、
“よく使う形”をヘルパーとして名前付きにしてしまうのが、業務レベルでの付き合い方として一番現実的です。
