fuzzy study

仕事・趣味で勉強したことのメモ

React入門 Redux

React入門本を読んで、Reduxについて勉強した。

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)

基本

  • Store
    • stateの唯一の保管先。「ここさえ見ればアプリの状態がすべてわかる」。
  • Reducer
    • stateの変更方法を定義するもの。
  • Action
    • Reducerに対してstateの変更をリクエストするもの。

store

createStore関数にReducerを指定して、storeオブジェクトを得る。

import { createStore } from 'react-redux';

const store = createStore(myReducer);

storeオブジェクトは以下のメソッドを持つ。

  • dispatch(action)
    • 与えられたActionに応じ、Reducerに定義された方法でstateを変更する。
  • subscribe(function)
    • dispatchのタイミングで発火するイベント関数を設定する
  • getState()
    • storeに保管されているstateを得る
  • replaceReducer(reducer)
    • storeオブジェクトに関連付けられたReducerを他のReducerに置き換える
    • なお、Reducerは複数定義しておいてcombineReducers関数でひとまとめにすることもできる。replaceReducerは動的にReducerを置き換えたい場合に使う。(らしい)

combineReducersは、大きなアプリなどでReducerを分割して開発するほうが効率が良い場合に使う。小さいアプリであれば1つのReducer内で分岐で処理しても問題ない。

Reducer

実体は関数。 storeのdispatchでactionが渡されたときに、その時のstateと渡されたactionを引数にとり、 actionに応じた処理を行う。

以下、一例。

function myReducer(state = initialState, action){
  switch(action.type){
    case 'MY_ACTION_1':
      return {
        ...state,
        hoge: action.payload.fuga,
      };
    default:
      return state;
  }
}

Action

Actionを作る関数をActionCreaterという。
後述のFSA(Flux Standard Action)に則ったオブジェクトを返すことが推奨される。

以下、一例。

const myAction1 = () => ({
  type: "MY_ACTION_1",
  payload: {
    fuga: "hello",
  }
})

myAction1をmyReducerにdispatchすると、stateに{ hoge: "hello" }が書き込まれる結果となる。

store.dispatch(myAction1());

Reactアプリでの使い方 without react-redux

とりあえず、storeオブジェクト経由でstateの管理を自由にできるので、stateに対して読み書きしたいコンポーネントのpropsにstoreを渡せばよい。
その場合、setStateは使わず、アプリをrenderする関数を作っておき、store.subscribeに登録することで、store更新があったときに再描画が走るようにする。

でもそれだとアプリが大きくなってきたときに、

といった問題があるので、react-reduxを使うとよい。

Reactアプリでの使い方 with react-redux

react-reduxでは、ReduxのstoreとReactのComponentを、connectという関数を介してつなげることで、疎結合なつくりにする。

主な要素は二つ。

Provider

storeを各コンポーネントのcontextを通じてアクセスできるようにするコンポーネント
自分はまだcontextの仕組みはよくわかっていないが、現状おまじない的にアプリの大元のコンポーネントをProviderで囲むようにする。

index.jsはこんな感じになる。

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './reducers';
import MyApp from './components/MyApp ';

const store = createStore(rootReducer);

render(
  (
    <Provider store={store} >
      <MyApp />
    </Provider>
  ),
  document.getElementById("root")
);

Container component

connect関数を使って作られるコンポーネント。 connect関数では、引数に渡す関数を通してコンポーネントのpropsにstateやactionへのアクセス用オブジェクトを渡す。

import { connect } from 'react-redux';
import { myMethod } from '../actions';
import MyAppfrom "../components/MyApp";

// mapStateToPropsでは、MyAppに渡したいオブジェクトを作ってreturn
function mapStateToProps(state) {
  return {
    ・・・
  };
}

// mapDispatchToPropsでは、MyAPpに渡したいactionをラップした関数をオブジェクトにしてreturn
function mapDispatchToProps(dispatch) {
  return {
    myMethod(value) {
      dispatch(myMethod(value));
    },
    ・・・
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(MyApp);

connectの第三引数にmergeProps関数も指定できる。

  • mergeProps
    • state, dispatchおよびコンポーネント自身のpropsをマージするロジックを書いた関数を渡す。
    • デフォルトではmapStateToPropsとmapDispatchToPropsの返り値オブジェクトを単にマージしたオブジェクトを返すようになっている。

基本的には、mapStateToPropsでそのコンポーネントに必要なstateを入れたオブジェクトを返し、 mapDIspatchToPropsでそのコンポーネントに必要なActionを定義した関数が入ったオブジェクトを返し、 mergePropsはデフォルトで使えばよい。
受け取り側のコンポーネントでは、mapStateToPropsとmapDispatchToPropsで返したstate/actionはprops.***で利用できる。

connect関数を書くファイルはContainer Componentと呼ばれる。 このようなコンポーネントを間に挟むことで、Reactのコンポーネントは純粋にViewの処理(+コンポーネントに閉じたstatefulな処理)のみを記述できる。

※対してReactのコンポーネントはPresentational Componentという。

index.jsはこうなる。

import React from 'react';
import { render } from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './reducers/reducers';
import App from './containers/App';
    
const store = createStore(rootReducer);
    
render(
  (
    <Provider store={store}>
      <App />
    </Provider>
  ),
  document.getElementById('root')
);

ためしに実践

Qiita記事検索アプリ

例によってQiita記事をキーワード検索するだけのアプリを作ってみた。

github.com

RPG風ショップアプリ

前から作ってみたかったお遊びアプリ。

github.com

設計について

設計順序とポイントとして、現状は以下がいいかなと思う。
何度か使っていけばもっといい方法が身についていくと思う。

  1. アプリに必要そうなstateを一通りreducerにinitialStateとして定義する。
    1. stateはコンポーネントではなくドメイン単位で考える。
    2. immutableな更新がしにくいため、ネストのあるオブジェクトには極力しない。
  2. ルート画面のコンポーネントを作り、必要なコンポーネントを洗い出す。
  3. コンポーネントをできる限りStatelessに作る。
  4. initialStateと各コンポーネントのpropsとactionを確認し、stateの過不足を検討してきれいにする。
  5. 必要なreducerを定義する。
    1. reducerはstateごとに作る。
    2. なるべく単純な操作を行うよう定義する。
  6. 必要なActionを定義する。
    1. まずは、reducerが管理するstateに対して単純な操作をするactionとして定義する。
    2. その後、複数のactionを組み合わせたり非同期処理を入れたりして、UIから直接使えるActionCreaterを定義する。
      (そのためには、本で紹介されているMiddlewareのredux-thunkをつかう)
  7. Container Componentを作る。

発展

以下の記事にて、reducerやactionをどう設計するかについて知見が紹介されている。

www.enigmo.co.jp

  • Reducer
    • Redux公式ドキュメントで、DomainState、 AppState、 UIState という3つのStateに分割することが提案されているとのこと。(Componentごとに分けるのではなく。)
  • Action
    • FSA(Flux Standard Action)を使うとよい、とのこと。
    • Actionの定義が人によってまちまちになりがちなので標準化したもの。