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
から始まり、currentValue
はarray[0]
である1
から始まります。
返り値はコールバックの結果、つまりaccumulator
+ currentValue
の1
となり、次のコールバックの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