reduce でひとつにまとめる
このレッスンで分かること
reduceは配列を 1 つの値にたたみ込みます。第 2 引数の初期値は必ず書きます- たたむ先は数値でなくてよく、オブジェクトにすれば複数の集計を一度に返せます
合計を出すたびに、外へ let を置いている
入門では合計を for ループで出しました。外に入れ物を用意し、1 件ずつ足していく形です。
JavaScript
const views = [120, 48, 200];
let sum = 0;
for (const n of views) {
sum += n;
}
// 368この「外に用意した入れ物」を引数として持ち歩くのが reduce です。第 1 引数が前回までの結果、第 2 引数が今見ている要素になります。
JavaScript
const total = views.reduce((sum, n) => sum + n, 0);
// 368最後の 0 が初期値です。1 回目の呼び出しでは sum に 0 が、n に先頭の要素が入ります。返した値が次の呼び出しの sum になり、最後の戻り値がそのまま reduce の結果になります。
| 回 | sum | n | 返す値 |
|---|---|---|---|
| 1 | 0 | 120 | 120 |
| 2 | 120 | 48 | 168 |
| 3 | 168 | 200 | 368 |
初期値は省略できますが、省略すると先頭の要素がそのまま初期値になります。この形で空配列を渡すと Reduce of empty array with no initial value で落ちます。初期値は必ず書く、と決めておくと事故がひとつ消えます。
初期値は「何と比べ始めるか」を決めている
初期値は数値に限りません。オブジェクトにすると、集計を複数まとめて持ち歩けます。タグの中でいちばん長い名前を探す例です。
JavaScript
const tags = ["HTML", "CSS", "JavaScript"];
const longest = tags.reduce(
(acc, tag) => {
return tag.length > acc.length ? { name: tag, length: tag.length } : acc;
},
{ name: null, length: -1 }
);
// { name: "JavaScript", length: 10 }比べかたを「より大きいとき」にしているのには意図があります。同じ長さが並んだとき、先に出てきたほうを残したいからです。>= にすると、あとから来た同点の要素で上書きされます。
初期値の length を -1 にしているのも同じ理由です。0 にすると、空文字しか無いときに 1 件も選ばれず、name が null のまま返ってしまいます。初期値の仕事は「絶対に負ける値を置いて、1 件目を必ず勝たせる」ことだと考えると決めやすくなります。合計なら 0、件数なら 0、最大なら十分に小さい値です。
accを書き換えて返す形も動きますが、新しいオブジェクトを返す形にしておくと、初期値のオブジェクトを使い回して結果が混ざる事故が起きません。
どこまで reduce にするか
reduce は何でも書けてしまうため、使いすぎると読みにくくなります。合計や最大や件数のように 1 つの値にまとめるなら reduce、件数を減らすだけなら filter、形を変えるだけなら map です。同じ結果になるなら、読んだ人に何が伝わるかで選びます。
0 件のときに何を返すかも、集計を書くたびに必ず決めます。合計なら 0、代表を選ぶなら null、といった具合です。初期値がそのまま返るように書いておけば、length を見る分岐は要りません。
集計値が数値なのか文字列なのかも 1 つに決めます。toFixed が返すのは文字列なので、そのあとで足し算をすると静かに文字列の連結になります。表示のための整形は、画面に出す直前に 1 か所でやるのが安全です。
よくある間違い
- コールバックで
returnを書き忘れる — 次の回の第 1 引数がundefinedになり、結果もundefinedになります
JavaScript
// undefined が返る
views.reduce((sum, n) => {
sum + n;
}, 0);- 引数の順番を取り違える — 第 1 引数が集計中の値、第 2 引数が要素です。
accとsumのように名前を書き分けておくと取り違えにくくなります
要件
- 関数名は aggregateViews とし、引数は作品の配列 works を 1 つだけ受け取る
- 戻り値は total と count と topTitle の 3 つのキーを持つオブジェクトにする
- total は views の合計、count は件数、topTitle は views が最大の作品のタイトルにする
- 同じ最大値が複数あるときは先に出てきたほうを選び、空配列のときは total と count を 0、topTitle を null にする
入出力例
aggregateViews([{"id":1,"published":true,"tags":["HTML","CSS"],"title":"プロフィールサイト","views":120,"year":2026},{"id":2,"published":true,"tags":["HTML"],"title":"自己紹介カード","views":48,"year":2026},{"id":3,"published":true,"tags":["HTML","CSS"],"title":"スキル一覧の表","views":32,"year":2025},{"id":4,"published":false,"tags":["JavaScript"],"title":"作品ギャラリー","views":8,"year":2025},{"id":5,"published":true,"tags":["JavaScript"],"title":"学習ログ","views":200,"year":2024}]) → {"count":5,"topTitle":"学習ログ","total":408}
aggregateViews([{"id":1,"published":true,"tags":["HTML","CSS"],"title":"プロフィールサイト","views":120,"year":2026}]) → {"count":1,"topTitle":"プロフィールサイト","total":120}
aggregateViews([{"id":4,"published":false,"tags":[],"title":"作品ギャラリー","views":0,"year":2025},{"id":5,"published":true,"tags":[],"title":"学習ログ","views":0,"year":2024}]) → {"count":2,"topTitle":"作品ギャラリー","total":0}
aggregateViews([]) → {"count":0,"topTitle":null,"total":0}