JavaScript
reactjs
React
redux

ソースコードリーディング: Redux

はじめに

人のコードを読むのはコーディング技術向上につながる。それが世界レベルで使われているものであればなおさらだ。そこで本稿ではReduxのソースコードを読んで、得られる知識を整理する。

読む範囲は、createStoreにかかわる部分。Reduxの本丸。createStoreのAPIStoreのAPI はしっかり読み込んで、仕様を正確に把握するとともに、どんな実装になっているかを読む前に想像しておく。もちろん、Reduxを触って動かしてどんなものかを把握するのも大事。

ソースは、GitHubにある。重点的に読み込むのは、 createStore.jsのソースコード。たった270行。まあJavaScriptで270行といったら結構多くの処理を書けるのでこれだけを読むだけでもそこそこ大変。

では始めよう。

ファクトリ関数

Reduxでstoreを作る時にはnewしない。そうではなく、createStoreという関数を呼び出して、Storeを作ってもらう。このようにユーザがnewする代わりにオブジェクトを作って返す関数が提供されている。このような関数をファクトリ関数という。

クラスそのものをAPIとして公開してユーザにnewさせるのではなく、ファクトリ関数をAPIとして提供するのがよいデザインであるといわれている。なぜか?うまく説明できる気がしないので、ググってほしい。

Storeはクロージャ―だった

createStore.jsの中をみると、class もなければ new もないし、 prototype もありんせん。じゃあ、Storeってなんなのよ、と思うが、関数がつまったただのオブジェクトである。実際、createStoreの最後をみるとこんな感じ。

export default function createStore(reducer, preloadedState, enhancer) {
// 変数定義
// getStateとかの関数定義
...
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

Storeの保持する各関数は、createStoreの内部で定義された変数にアクセスでき、また変数を共有している。つまりクロージャ―だね。JavaScriptではクラスよりもクロージャ―の方が自由なのである。なぜか?うまく説明できる気がしないので、ググってほしい。

内部変数の役割を読み解く

内部変数は以下のように定義されている。全てletであり、Storeの各種関数によって変更されることが示唆される。

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false
  • currentState

getState()で取得できる状態を示す。これは特に説明不要かな。

  • currentReducer

StoreのReducerを表す。currentState = currentReducer(currentState, action)のように使われている。私たちが実装するReducerのシグネチャを前提としているね。なお、Storeは作った後にReducerを変更することができる。つまり、replaceReducer()。そのためにこれをletで宣言している。

  • currentListeners
  • nextListeners

登録されたlinsterを配列として保持している。私たちがsubscribe()でlistenerを登録する際には、まずnextListerに登録される。dispatchの最後に以下のコードで、登録された全てのリスナを起こしてあげている。

    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
  • isDispatching

ディスパッチ中であるか否かのフラグ。

Observableとしての振る舞い

tc39 という謎の集団がJavaScriptにObservableという新しい型を導入しようともくろんでいる。Observableはイベントストリームのようなものを抽象化したようなものである。たしかにReduxはアクションによって断続的にStateが変化するものであるという性質を持っており、StoreはObservable型として実装するのは悪くないアイディアだ。

実際、ドキュメントでは以下のように書かれている。

The Store is also an Observable, so you can subscribe to changes with libraries like RxJS.

Observable型のインターフェースは ここ で定義されている。Reduxでは、StoreをObservableにするために必要な処理をすべてobservableで定義して、以下のようにStoreのシンボルに突っ込んでいる。

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

このメソッドを呼んであげると、StoreのObservableを取得することができる。もっとも実装をみると、最低限しか実装していないけど(実装しているのはsubscribeメソッドだけ)

最後に

触れなかったけど applyMiddleWare 以外の拡張ができる(enhancer)のに初めて気づいた。どんな実装があるんだろう。