JavaScript
React
redux

【React.js】createStoreを実装してみたらReduxの理解が捗った

Reduxの使い始めにActionやらReducerやらStoreやら新しい概念が出てきて混乱しました。
その時にcreateStoreを実装してみたら流れをよく理解出来たので紹介します。

Reduxでカウンターを実装

シンプルなカウントアプリを作成。今回はstate更新の流れを確認したいので、ActionCreatorやstateのイミュータブルは一旦置いておきます。

index.html
<div id="root"></div>
カウントアプリ.js
import { createStore } from 'redux';
import React from 'react';
import ReactDOM from 'react-dom';

// Action
const myAction = { type: 'ACTION_INCREMENT'};

// Reducer
const myReducer = (currentState = 0, action) => {
  switch(action.type) {
    case 'ACTION_INCREMENT':
      return currentState + 1;
    default:
      return currentState;
  };
};

// storeにreducerを登録
const store = Redux.createStore(myReducer);

const render = () => ReactDOM.render(
  <h1>{store.getState()}</h1>,
  document.getElementById('root')
);

// storeが更新(dispatch実行時)されたら実行される
store.subscribe(render);

// クリック時にstateの更新を行う
document.addEventListener('click', () => store.dispatch(myAction));

render()

Action
stateをどう変更するかという情報を返しています。
↑では「ACTION_INCREMENT」が該当します。

Reducer
Actionをを引数として受取り、stateをどう更新するかを定義します。
↑では「ACTION_INCREMENT」を受取り、currentStateにインクリメントしています。
※Reduxではstateに手を加えないという作法があり、Object.assign()を使って新しいstateを返すのが正しいやり方ですが今回は無視します。

subscribe
subscribe()は、dispatch()される度に指定した処理を実行します。
↑ではクリックイベントにdispatch()を登録し、クリックされる毎にrender()を実行しています。

dispatch
引数にActionを指定し、Reducerが呼ばれます。dispatch()が呼ばれる度にstateが更新されsbuscribe()が実行されます。

createStore()の中身はどうなっているのか?

Reduxを初めて見た時はdispatchやsubscribeが何をしているのか理解出来ませんでした。
Reduxフレームワークに隠れているからです。dispatchもsubscribeもcreateStoreが持つ関数ですのでcreateStoreの中身を見ると何をしているのか分かります。

createStoreを実装することで全体の流れを理解出来るようになりました。今回は自分なりに実装してみます。

createStoreを実装してカウントアプリ.js
//import { createStore } from 'redux';
import React from 'react';
import ReactDOM from 'react-dom';

// createStore()を実装
const myCreateStore = (reducer) => {
  let state;
  let listeners = [];

  // getStateはただstateを返すだけ
  const getState = () => state;

  // dispatchはreducerを呼んでstateを更新する
  // subscribeで登録した処理を実行
  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };

  // subscribeでdispatch時に呼ぶ処理を登録
  const subscribe = (listener) => {
    listeners.push(listener);
  };

  dispatch({});

  return { getState, dispatch, subscribe };
};

// Action
const myAction = { type: 'ACTION_INCREMENT'};

// Reducer
const myReducer = (currentState = 0, action) => {
  switch(action.type) {
    case 'ACTION_INCREMENT':
      return currentState + 1;
    default:
      return currentState;
  };
};

// storeにreducerを登録
// const store = Redux.createStore(myReducer);
const store = myCreateStore(myReducer);

const render = () => ReactDOM.render(
  <h1>{store.getState()}</h1>,
  document.getElementById('root')
);

// storeが更新(dispatch実行時)されたら実行される
store.subscribe(render);

// クリック時にstateの更新を行う
document.addEventListener('click', () => store.dispatch(myAction));

render()

Codepen上で試しただけですが問題なくカウントされました。

隠蔽されていたcreateStore()を見てみると、
dispatch:Reducerを実行してstateの更新、subscribeで登録した処理の実行
subscribe:dispatch時に実行する処理の登録
をしているのがよく分かるんじゃないかと思います。

それぞれの役割を見るとReduxは決して複雑なものではなくシンプルなものだと思うようになりました。
初学者の方の参考になれば幸いです。