React Redux TutorialでReduxのデータフローを確認する

備忘録


ソースコード引用元

www.valentinog.com

Redux

ReduxはFluxライクなフレームワーク
データの流れが一方通行しつつ、状態管理を行う。
Vuexを利用したことあるならば、Vuex ≒ reduxと考えるとわかりやすい。

違いはVuexにおけるMutationの役割をreduxではreducerが担っている。
また、reduxではStoreの値はイミュータブルとして扱われるため、
reducerで新しい値を設定する際に、新しいオブジェクトとして再設定する必要がある。

参考 Qiita - Fluxとはなんなのか

Reduxを使用したデータフロー

  1. Componentからdispatchが実行
  2. dispatchがActionを実行
  3. reducerがStoreにstateを設定

1. Componentがdispatchを実行

// src/js/components/Form.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addArticle } from '../actions/index';

function mapDispatchToProps(dispatch) {
  return {
    addArticle: article => dispatch(addArticle(article))
  };
}

class ConnectedFrom extends Component {
  constructor(props) {
    super(props);
    this.state = {
      title: ''
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  };

  handleChange(event) {
    this.setState({ [event.target.id]: event.target.value });
  };

  handleSubmit(event) {
    event.preventDefault();
    const { title } = this.state;
    this.props.addArticle({ title });
    this.setState({ title: '' });
  };

  render(){
    const { title } = this.state;

    return (
      <form onSubmit={this.handleSubmit}>
        <div>
          <label htmlFor="title">Title</label>
          <input
            type="text"
            id="title"
            value={title}
            onChange={this.handleChange}
          />
        </div>
        <button type="submit">SAVE</button>
      </form>
    );
  };
};

const Form = connect(
  null,
  mapDispatchToProps
)(ConnectedFrom);

export default Form;

ボタンをクリックするとsubmitが実行される単純なFormコンポーネント
注目すべきは6行目のmapDispatchToProps関数と54行目のconnect

6行目のmapDispatchToPropsは名前の通り、DispatcherPropsに受け渡す関数。
ここではactionからimportしてきたaddArticle関数をFromコンポーネント
プロパティ(Props)に設定する役割を担っている。

また、mapDispatchToProps(Redux)とコンポーネント(React)を接続しているのが、
54行目のconnectが担っている。
connectはReactがReduxの管理している状態(Store)にアクセスするための関数なので必須。

connectを行うことによって、
コンポーネントはPropsからReduxのdispatchにアクセスすることができる。
(30行目 this.props.addArticle({ title });)

引用させて頂いているコード例ではconnectを使用して、
ReactからReduxのStoreにアクセスしているが、Redux Hooksを使用すると簡単にアクセスできるらしい。
参照 Qiita - Redux Hooks によるラクラク dispatch & states

2. dispatchがActionを実行

// src/js/actions/index.js
import { ADD_ARTICLE } from '../constants/action-types';

export function addArticle(payload) {
  return {type: ADD_ARTICLE, payload};
};

export function getData() {
  return function(dispatch){
    return fetch('https://jsonplaceholder.typicode.com/posts')
    .then(response => response.json())
    .then(json => {
      dispatch({ type: 'DATA_LOADED', payload: json });
    });
  };
};

Fromコンポーネントdispatchから4行目のaddArticleを実行している。
addArticle関数は戻り値にtypepayloadを持ったオブジェクトを返し、
このオブジェクトはreducerが受け取る。
また、Actionは主に、ビジネスロジックAPI実行を受け持つ。

3. reducerがStoreにstateを設定

// src/js/store/index.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers/index';
import { forbiddenWordsMiddleware } from '../middleware'

const store = createStore(
  rootReducer,
  applyMiddleware(forbiddenWordsMiddleware)
);

export default store;

Storeの本体。
ReduxのcreateStoreでStoreを作成している。
また、第一引数にreducerを設定することで、reducerがStoreの状態管理を行う。

// src/js/reducer/index.js
import { ADD_ARTICLE } from '../constants/action-types';

const initialState = {
  articles: [],
  remoteArticles: []
};

function rootReducer(state = initialState, action){
  if (action.type === ADD_ARTICLE) {
    return Object.assign({}, state, {
      articles: state.articles.concat(action.payload)
    });
  }

  if (action.type === 'DATA_LOADED'){
    return Object.assign({}, state, {
      remoteArticles: state.remoteArticles.concat(action.payload)
    })
  }
  return state;
};

export default rootReducer;

reducerはStoreの初期値の設定や、actionが実行されたときの状態管理を行う。
この例では2. dispatchがActionを実行addArticle関数が戻り値として返すオブジェクトを、
rootReducerが受け取る。
受取ったオブジェクトは引数のactionで受取って、actionによってStoreの状態を遷移させる。

reducerの注意点としては、Reduxのstateはイミュータブルなオブジェクトなため、
Object.assignを使用して、オブジェクトのコピーによって状態を変える必要がある。


Vuexの経験があったから状態管理の遷移はわかり良い感じだったけど、
ComponentとStoreの接続部分がVuexと比べて冗長に感じる。
Redux Hooksを使えば、簡潔に接続できるのかな。