はじめに
JavaScriptのreduceメソッドは、配列の要素を操作するための関数です。データの集約や変換など、さまざまな場面で活躍するメソッドですが、その柔軟性ゆえに最初は理解しづらいこともあります。本記事では、reduceメソッドの基本から、実際のプロジェクトで役立つ(かもしれない)実用的な使用例までを解説します。
1. reduceメソッドとは?
reduceは、配列の各要素を順番に処理し、1つの結果に「集約」するためのメソッドです。単純な合計計算から、複雑なデータ構造の変換まで、幅広く利用できます。
MDNでは、reduceメソッドは次のような引数をとると説明されています。
arr.reduce(callback(accumulator, currentValue), initialValue)
この定義は少し理解しづらいですが、簡単に言うと、reduceは配列の要素をひとつずつ処理し、その結果を「累積値」(accumulator)にまとめていくものです。
覚えておくべきは、以下の3点です
- reduceは2つの引数をとり
- 第一引数に関数、第二引数に初期値を入れる
- 配列は、順に第一引数によって処理されていき、最後には累積値(accumulator)が返される
2. 基本的な使用方法
reduce
の最も基本的な使用方法であるのは、"配列の合計値を求める"ものです。
たとえば、以下は数値の配列の合計を計算しています。
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => {
return accumulator + currentValue;
}, 0);
console.log(sum); // 15
上記の例での処理を順に追っていくと以下のように説明できます。
- 最初にaccumulatorが初期値の0に設定されます
- 配列の最初の要素1がcurrentValueとして渡され、accumulatorに加算されます(結果は1)
- 次に2がcurrentValueとして渡され、累積値1に加算されます(結果は3)
この処理が配列の最後の要素まで続き、最終的に合計値(累積値)として15が返されます。
3. reduceの引数
reduceは以下の2つの引数を受け取ります:
コールバック関数: 各要素ごとに呼び出され、accumulator(累積値)とcurrentValue(現在の要素)を引数として受け取ります。
初期値(オプション): accumulatorの初期値です。指定しない場合、配列の最初の要素が初期値として使われますが、これはバグの原因になりやすいためできるだけ初期値を明示的に設定するのがおすすめです。
コールバック関数の詳細
コールバック関数には、以下の4つの引数が渡されます:
accumulator: 前回の処理で返された値、または初期値。
currentValue: 現在の要素。
currentIndex(オプション): 現在のインデックス
array(オプション): 現在処理している配列そのもの。
array.reduce((accumulator, currentValue, currentIndex, array) => {
// 処理
}, initialValue);
4. 実用的な使い方の例
ここからは、実際にreduceを使用してどんなことができるのか、実用的な例を紹介していきます。
数値の合計を求める
const numbers = [10, 20, 30, 40];
const total = numbers.reduce((sum, num) => sum + num, 0);
console.log(total); // 100
オブジェクトの配列の値を集約する
先ほどの例の応用です。オブジェクトの中にあるageを集計しています。
const users = [
{ name: 'AAA', age: 25 },
{ name: 'BBBB', age: 30 },
{ name: 'CCC', age: 35 }
];
const ageSum = users.reduce((totalAge, user) => totalAge + user.age, 0);
console.log(ageSum); // 90
パイプライン処理
今までと比べちょっとトリッキーですが、パイプライン処理(関数を順に実行)をすることもできます。
可読性は若干低いですがカッコ良さは随一ですね。(個人の感想)
const pipeline = [
(str) => str.toUpperCase(),
(str) => str.replace(" ","").split('').join('_'),
(str) => `出力: ${str}`
];
const processString = (str) => {
return pipeline.reduce((acc, fn) => fn(acc), str);
};
console.log(processString('hello world'));
// "出力: H_E_L_L_O_W_O_R_L_D"
非同期関数も同じようにして実行できます。
複数の非同期処理を同時に、というとPromise.all
を使用する方法が有名かと思いますが、reduce
を使用すると複数の非同期関数を順番に実行することができます。
APIリクエストで得たデータを次のリクエストのパラメータとして使う場合などに非常に有効です。
5. reduceのデメリット
reduceのデメリットといえばなんといっても可読性の低下です。
柔軟性が高く、さまざまなことができる一方で、ロジックが複雑になると途端に可読性がものすごく低下する可能性が多分にあります。
そのような場合は、処理を適切に分割したり、代用できるメソッドがないかを確認しましょう。
例えば、以下のコードはネストされた配列のフラット化を行っています。
const nestedArray = [[1, 2], [3, 4], [5, 6]];
const flatArray = nestedArray.reduce((acc, curr) => acc.concat(curr), []);
console.log(flatArray); // [1, 2, 3, 4, 5, 6]
しかし、この場合flat
を使用すればもっと平易に、短く書けます。
const nestedArray = [[1, 2], [3, 4], [5, 6]];
const flatArray = nestedArray.flat();
console.log(flatArray); // [1, 2, 3, 4, 5, 6]
このように、reduceを使用するかどうかは場面によって異なります。
さいごに
reduceについては最初は難解に感じるかもしれませんが、実際に手を動かして試してみることで、動きは理解できるようになります。
活かせる場面は他のメソッドよりも少ないかもしれませんが、ここぞという場面で有効に使えることがあります。ぜひ、自分のプロジェクトや問題解決に活用してみてください!!