reduxのcomposeが何をしているかちょっと覗いてみた

最近、業務でreactを使用しています。 その中でredux周りのライブラリを使うことが多いです。そんなreduxと仲良くしたいと思い今回はcomposeがどんなことをしているのか見ていきたいと思います。

そもそもcomposeって?

composeは任意の数の関数を引数に受け取りその関数を右から左の順番に実行していきます。 以下のコードではcomposeを使用してapplyMiddlewareでstoreを拡張する方法と、redux-devtoolsからいくつかの開発ツールを使用する例が挙げられています。 (公式サイトから引用)

import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import DevTools from './containers/DevTools'
import reducer from '../reducers'

const store = createStore(
  reducer,
  compose(
    applyMiddleware(thunk),
    DevTools.instrument()
  )
)

実際のcomposeのコード

github.com

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

中身は10行程でとてもシンプルです。

そこで大事なのが最後の行の箇所です。

return funcs.reduce((a, b) => (...args) => a(b(...args)))

高階関数について

上記のコードがどのようになっているかを理解するためには高階関数を知る必要があります。実際に上記のコードと同じようなものを書いてみます。

const first = num => num + 1;
const second = num => num * num;

function compose(f1, f2){
  return (value) => {
    return f1(f2(value))
  }
}

const result = compose(first, second);
console.log(result(3)); // 10

まず引数の値に1足した値を返す関数aと、引数の値を二乗した値を返す関数bを用意しました。 そしてcompose関数を作成しました。この関数の特徴としては以下が挙げられます。

  • 引数に関数を受け取る
  • 戻り値として関数を受け取る

returnしている箇所に注目してみます。

return (value) => {
    return f1(f2(value))
}

f1関数が実行されるとf2関数が実行されます。 f1f2の戻り値を引数にもつので、ここでいうとvalueを2乗した結果が入ります。composeの仕組みとしては今説明したものになります。ですが今のままのコードですとcomposeのように任意の数の引数を受け取ることができず、また引数の数だけ関数を実行することができません。そのためコードを公式サイトと同じようにしていきます。

まず引数の数を固定にせず、呼び出すとき任意の数だけ受け取れるようにします。 上記のコードを修正します。

function compose(...funcs){
  return (value) => {
    return f1(f2(value))
  }
}

こうすることでfuncsには引数に設定した関数の数だけ入った配列が入ってきます。 これで引数に任意の数だけ関数を渡せるようになりました。

次に引数の数だけ関数を実行できるようにします。

function compose(...funcs){
  return funcs.reduce((before, after) => (...args) => before(after(...args)))
}

ポイントはreduceを使うことでfuncsに入っている関数を順に取り出してきて、その中のコールバック関数で右にある関数から順番に実行されていきます。

const first = num => num + 1;

const second = num => num * num;

const third = num => num * num * num

function compose(...funcs){
  return funcs.reduce((before, after) => (...args) => before(after(...args)))
}

const result = compose(first, second, third)(3);
console.log(result);

新たにthird関数を作成してみてcompose関数の引数に三つの関数を渡してみました。 このファイルを実行した結果をみてみます。

720

意図した通りに結果が出力されました。

まとめ

reduxのライブラリを読む上で高階関数やJsのスプレット演算子の使い方などを学ぶことができました。今度はcompose以外のredux周りのライブラリを解読していきたいと思います。