【JavaScript】reduce(Object.assign)で失敗した

コレクションの配列をオブジェクトにしようとして、こんな感じのコードをよく書くのだけど、

const users = [{
  id: 1,
  name: 'suzuki'
}, {
  id: 2,
  name: 'sato'
}]

const usersById = users
  .map((user) => ({[user.id]: user}))
  .reduce((obj, userObj) => Object.assign(obj, userObj), {})

console.log(usersById)
// { '1': { id: 1, name: 'suzuki' },
//   '2': { id: 2, name: 'sato' } }

ふと、reduceに適用している関数は Object.assign なんだからこう書けるんじゃないかと思ったけど、

const usersById = users
  .map((user) => ({[user.id]: user}))
  .reduce(Object.assign, {})

なんと意図した動きにならなかった。実行結果がこうなる。

const usersById = users
  .map((user) => ({[user.id]: user}))
  .reduce(Object.assign, {})

console.log(usersById)

// { '0': { '1': { id: 1, name: 'suzuki' } },
//   '1': { '2': { id: 2, name: 'sato' } },
//   '2': { id: 2, name: 'sato' } }

理由は、reduce の仕様にある。

reduce のコールバックに与える関数には4つの引数を与える。

  1. previousValue - 現在処理されている配列要素の 1 つ前の要素。もしくは、initialValue。
  2. currentValue - 現在処理されている配列要素
  3. index - 現在処理されている配列要素のインデックス
  4. array - reduce に呼ばれた配列

なので、Object.assign をそのままコールバック関数に与えると、Object.assign(previousValue, currentValue, index, array) を実行してしまう。それで上記のような思わぬ結果になった。

結論としては、 array.reduce(Object.assign, {}) としてはいけない。