reduceを制する者はJSを制する
2022-12-13
azblob://2022/12/12/eyecatch/2022-12-13-reduce-usage-000.jpg

みなさまお久しぶりです。

春にFIXERに入社し、気づけば忘年会シーズンに突入している事実に驚愕している雄大くんです…    
入社してからはフロントエンド、バックエンド、インフラなど自分の中にあったWeb技術の垣根を越えて仕事をしていたため、確実に春からは成長していると思っています。

そんな僕がフロントエンドを書く際に使用しているのはJavaScriptもしくはTypeScript(以下JS, TS)です。    
これらの言語を使用する際にAPIと通信し、配列やオブジェクトを扱う機会がよくあると思います。    
僕がJS, TSを触り始めてから一番最初に挫折しそうになったのは配列操作だったので、一人でも多くの人が配列を思い通りに動かせるようになればと思います。

最強のMethod "reduce"

配列といっても皆さんは何を思い浮かべるでしょうか?    
代表的な配列を扱う方法としてFor文を思い浮かべると思います。    
これ以外にもMapやSet、filter、mapが思いついた人もいるでしょう。

今回僕が紹介するのは、型が配列であればデータを創造することさえできる最強メソッドである"reduce"です。

サンプルを用いて使い方から説明していきます。

Javascriptconst array: number[] = [1, 2, 3, 4, 5];
console.log(`array:${array}`);
const result = array.reduce((acc, currentValue, index) => {
  const double = currentValue * 2;
  return [...acc, double];
}, []);
console.log(`result: ${result}`);

2022-12-13-reduce-usage-002

サンプルのコードは数値型の配列の中身を取り出し、倍にして新しい配列を生成しています。    
reduceを使用する場合は引数が2種類必要になります。

Callback関数

いきなりなんぞや?と思うかもしれませんが、要するに現在の要素を処理する際に何をしたいかを記述する関数です。
以下の写真がサンプルコードのCallback関数の中身です。

2022-12-13-reduce-usage-002

関数を書く上でreduceから渡すことができる引数が4種類ありますが、ここでは主に使用する3種類を紹介します。

  • 第一引数(Accumulator)    
    Accumulatorと呼ばれる初期値のような値です。前回のCallback関数の結果が入ります。    
    最初に呼ばれたときは第2引数であるInitialValueの初期値を使用し、もしInitialValueの値がない場合は配列の一番最初の値が挿入されます。    
    ここにデータが蓄積されていくので、累算機という意味のAccumulatorが使われます。(書き方は人それぞれですが、僕はよくaccって書きます)
  • 第二引数(CurrentValue)    
    現在の要素が入ります。(今回の場合は1,2,3,4,5が順番にvalに入っていく)
  • 第三引数(Index)    
    現在の要素の位置が入ります。

ここでの注意点は第一引数についてです。
上でも書いてあるように、第一引数には前回の要素の処理の結果が入ってきます。
しかし、結果を返してあげなければ関数内に残したまま次の処理に移ってしまうので、第一引数の値は必ずreturnして次の処理に引き継ぎましょう。

※正確に言うと、処理した結果の配列などを返してあげる必要があります。

InitialValue

これは最初にCallback関数が呼び出された際に使用するCallback関数の第一引数の初期値を設定します。    
また、InitialValueが設定された場合はInitialValueの最初の値にCurrentValueが置き換わります。(今回は空の配列なので、何も入りません)

2022-12-13-reduce-method-usage-003

以上がざっくりとしたreduceの使い方です。個人的にはCallback関数の部分が難しく、再帰的なコードがわからなかったので苦労しました。    
CurrentValueに対して何をしたいかを明確にすることで、処理を書きやすくなると思います。
例:2で割れる値だけを抽出したい、名前だけを抽出したいなどなど...

上記の使い方だけだとmapやfor文でいいじゃん!!と思ってしまうでしょう。しかし、reduceの真骨頂は新しい配列を生成できる部分ではありません。    
reduceを使いこなすことで、ページネーションなどの複雑な処理も実装することができます。

ページネーションを実装してみよう

以下ざっくりとしたサンプルですが、ページネーションを実装しています。ご参考までに。

Javascriptconst data = [
  {
    "name" : "山田太郎",
    "address" : "東京都",
    "mail" : "hogehoge@hogehoge.com"
  },
  {
    "name" : "山田太郎",
    "address" : "東京都",
    "mail" : "hogehoge@hogehoge.com"
  },
  {
    "name" : "山田太郎",
    "address" : "東京都",
    "mail" : "hogehoge@hogehoge.com"
  },
  {
    "name" : "山田太郎",
    "address" : "東京都",
    "mail" : "hogehoge@hogehoge.com"
  },
]

const createPageObj = () => {
 const pageItemsCount = 3
 const pageItems = data.reduce((acc, val, index) => {
   const page = Math.floor(index / pageItemsCount)
   if(acc[page] === undefined) {
     acc[page] = []
   }
   acc[page].push(val)
   return acc
 },[])
 
 console.log(pageItems)
}

Result

2022-12-13-reduce-usage-004

コードの解説を少々します。

  • pageItemsCountでは一ページに表示する上限の件数を設定しています。    
    要素の位置を上限の件数で割ることで何ページ目に現在のデータがあればいいかがわかるので、    
    小数点以下を切り捨てにしてページ数を算出します。
  • 配列に値を追加する際に該当する要素がなかった場合はエラーになってしまうので、要素がundefinedだった場合は空の配列を追加します。
  • あった場合はそのまま値を配列に追加します。

以上がページネーションの実装方法になります。ほかにも追加要素として現在のページ数やページ内の記事が何番目の記事なのかなどの情報も付与できるので、実装してみてください。

reduceについていかがだったでしょうか?すこしでも理解が深まれば幸いです。