reduceは複数の引数を取る関数をコールバックに取るメソッドでfilterやmapに比べて使うときに気をつけなければならない点が多いのでよくやるエラーについて解説します。
コールバックの返リ値とinitialValueは配列と同じ形式
reduceでは左から右(右から左)に処理をし返り値は新たな引数として再利用する。
そのため返り値は新たな引数(配列要素)と同様の振る舞いをしないとエラーになる。
initialValueも与えた最初の配列要素としてみなされるのでまた配列要素と同じ形式でないとならない。
例えばObjectのある要素の合計を求めたいとき
const arr=[ { num: 1}, {num:2}, .... {num: } ];
// NG 返り値が num属性をもったObjectでない NaN
arr.reduce((previous, current)=>{ return previous.num+current.num; });
// OK 最終結果は { num: 1+2+3+...+n }
arr.reduce((previous, current)=>{ return { num: previous.num+current.num }; });
// NG initialValueが num属性をもったObjectでない { num: NaN }
arr.reduce((previous, current)=>{ return { num: previous.num+current.num }; }, 0);
タチが悪いことにこれらはundefinedと数字の演算でNaNとなり例外を出さずどこかにNaNを返す。
initialValueを渡す時と渡さない時の挙動
ではinitialValueを渡さなければどうなるか?
2乗和を求めるプログラムの以下の例を見てもらおう。
const arr=[ 3 ];
const arr=[ 3, 4 ];
console.log(arr.reduce((a,b)=>{ return a+b*b; })); // 3
console.log(arr.reduce((a,b)=>{ return a+b*b; }, 0)); // 9
console.log(arr2.reduce((a,b)=>{ return a+b*b; })); // 19
console.log(arr2.reduce((a,b)=>{ return a+b*b; }, 0)); // 25
コールバックがない場合はひとつ目の要素の2乗部分が呼ばれず正しい値にならない。
からの配列を渡すとどうなるか?
const arr=[];
console.log(arr.reduce((a,b)=>{ return a+b*b; })); // TypeError
console.log(arr.reduce((a,b)=>{ return a+b*b; }, 0)); // 0
TypeErrorの例外が投げられる。initialValueを与えればinitialValueが返ってくる。
これは単にinitialValueを返しているので{ num: 0 }
とか与えるとそのまま返ってくる(もちろん要素があれば変な値になる)。
まとめ
安全にreduceを使うためにはinitialValueは与えコールバック関数の返り値とinitialValueは配列要素と形式を合わせるべきです(使うものだけで多分動きます)。
数字などを扱っているときは便利ですがちょっと複雑なオブジェクトを扱うときは注意が必要です。