JavaScript配列をreduce()で操作する方法と注意点

Reactを勉強していたら初めてreduceメソッドを知ったのでまとめました。

reduce()は配列の各要素に対してコールバック関数を実行し、その結果を次のコールバック呼び出し時に渡していき、配列の全ての要素に実行した結果を返します。

説明だけだとよく分からないので、まずはコードを見てみます。

const array = [1, 2, 3, 4, 5];

const result = array.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
}, 0);

console.log(result); // 15

初回のaccumuratorは第二引数に渡した0から始まり、currentValuearray[0]である1から始まります。 返り値はコールバックの結果、つまりaccumulator + currentValue1となり、次のコールバックのaccumulatorの値となります。このように、accumulatorに関数の結果が蓄積されていき、最終的に全要素の計算がされた結果が返ることになります。

処理の流れ:

呼び出し accumulator currentValue currentIndex 返り値 (accumulator + currentValue)
1 (初回) 0 1 0 1
2 1 2 1 3
3 3 3 2 6
4 6 4 3 10
5 10 5 4 15

reduceの構文

処理の流れが分かったところで、改めて構文を確認します。

reduce(callbackFn)
reduce(callbackFn, initialValue) // initialValueは省略可

引数:

  • callbackFn: 配列の各要素に対して実行される
  • initialValue(省略可): コールバックが最初に呼び出された時の、その引数である accumulator が初期化される値

戻り値: 配列全体にコールバック関数を実行した結果

コールバックの構文:

callbackfn: (accumulator, currentValue, currentIndex, array)
引数名 説明
accumulator 前回のコールバックの結果。初回はinitialValueが指定されていればその値、なければarray[0]
currentValue 現在処理されている配列の要素。初回はinitialValue指定時はarray[0]、指定なしはarray[1]
currentIndex currentValueの位置(インデックス)。initialValueが指定されれば0から、なければ1からスタート。
array reduceメソッドを呼び出した元の配列。

ちなみに、accumulateは「蓄積する」という意味。関数の結果を蓄積していく引数なのでaccumulator

reduceの使用例

例えばECサイトでほしい物リストがあり、その数量と金額からリスト全体の合計金額を求める例です。

const wishList = [
  { name: "おしゃれな本棚", price: 10000, quantity: 2 },
  { name: "でかい机", price: 15000, quantity: 1 },
  { name: "疲れない椅子", price: 8000, quantity: 3 }
];

const total = wishList.reduce((accumulator, item) => {
  return accumulator + item.price * item.quantity;
}, 0);

console.log(`合計金額: ¥${total}`); // 合計金額: ¥59000

注意点: 初期値を省略したら?

結論から言うと、引数は省略しない方がいいと思っています。なぜなら、以下で説明するように意図しない結果を引き起こすリスクになるかもしれないからです。

異なる種類のデータを計算しようとする

第二引数(initialValue)は省略可能で、冒頭の合計を計算するサンプルコードでは初期値0を省略したらいいのでは?と思うかもしれません。実際、冒頭のコードは次のように0を省略しても問題なく同じ結果を得ることができます。

const array = [1, 2, 3, 4, 5];

const result = array.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
}); // 第二引数(initialValue)0を省略した場合

console.log(result); // 15

処理の流れ

呼び出し回数 accumulator currentValue currentIndex 返り値 (accumulator + currentValue)
1 (初回) 1 2 1 3
2 3 3 2 6
3 6 4 3 10
4 10 5 4 15

しかし、使用例にあげたwishListの合計計算のコードではどうでしょうか?意図しない結果が返るはずです。

const wishList = [
  { name: "おしゃれな本棚", price: 10000, quantity: 2 },
  { name: "でかい机", price: 15000, quantity: 1 },
  { name: "疲れない椅子", price: 8000, quantity: 3 }
]; // 第二引数(initialValue)0を省略した場合

const total = wishList.reduce((accumulator, item) => {
  return accumulator + item.price * item.quantity;
});

console.log(`合計金額: ¥${total}`); // 合計金額: ¥[object Object]1500024000

第二引数(initialValue)を省略した場合のaccumulatorの初期値はarray[0]となります。つまり、最初の要素(オブジェクト)自体になります。その結果、オブジェクトと数値を足そうとすると、オブジェクトが文字列に変換され、期待しない文字列の連結が発生してしまいます。

// 初期値を省略した場合の初回の計算
// accumulator  = array[0] = { name: "おしゃれな本棚", price: 10000, quantity: 2 }
// currentValue = array[1] = { name: "でかい机", price: 15000, quantity: 1 }

{ name: "おしゃれな本棚", price: 10000, quantity: 2 } + 15000

空の配列に対するエラー

例えば先ほどのwishListはAPIから取得したデータを格納する配列とすると取得結果は空になることも想定されます。初期値を指定した場合は問題なく0という計算結果になります。

const wishList = []; // APIからの取得した結果、空の配列となった想定

const total = wishList.reduce((accumulator, item) => {
  return accumulator + item.price * item.quantity;
}, 0);

console.log(`合計金額: ¥${total}`); // 合計金額: ¥0

しかし第二引数(initialValue)を省略すると、配列が空の場合reduceは何を返すべきかわからず、TypeErrorを投げます。

TypeError: Reduce of empty array with no initial value

参考ソース