React Redux TutorialでReduxのデータフローを確認する
備忘録
ソースコード引用元
Redux
ReduxはFluxライクなフレームワーク
データの流れが一方通行しつつ、状態管理を行う。
Vuexを利用したことあるならば、Vuex ≒ reduxと考えるとわかりやすい。
違いはVuexにおけるMutation
の役割をreduxではreducer
が担っている。
また、reduxではStoreの値はイミュータブルとして扱われるため、
reducer
で新しい値を設定する際に、新しいオブジェクトとして再設定する必要がある。
Reduxを使用したデータフロー
- Componentからdispatchが実行
- dispatchがActionを実行
- 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
は名前の通り、Dispatcher
をProps
に受け渡す関数。
ここでは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
関数は戻り値にtype
とpayload
を持ったオブジェクトを返し、
このオブジェクトは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を使えば、簡潔に接続できるのかな。