React Redux - Hooks APIを使用してReactコンポーネントとReduxストアを接続させる
やりたいこと
- ReduxのHooksAPIを使用して
connect()
を使用せずにReactからReduxに接続する
公式ドキュメント
react-redux.js.org
下記チュートリアルのソースをベースにリファクタリングを行う
www.valentinog.com
初めに(注意点)
Hooks API(useSelector
とuseDispatch
)は関数コンポーネントでのみ機能します。
そのため、Hooks APIを使用したい場合は、Classコンポーネントは関数コンポーネントに変更する必要があります。
reactjs.org
1. アプリケーションをProvider
でラップ
// src/index.js import React from 'react'; import { render } from 'react-dom'; import { Provider } from 'react-redux'; import store from './js/store/index'; import App from './js/components/App'; render ( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
アプリケーション全体をProviderでラップし、
Storeをコンポーネント全体で利用できるようにする。
2. useSelector
関数を使用して、ストアからデータを取り出す
const result : any = useSelector(selector : Function, equalityFn? : Function)
useSelector
関数を使用することで、コンポーネントからReduxのstate
にアクセスすることができる。
connect
を使用していたコンポーネントをuseSelector
にリファクタリングを行うと、下記のように書くことができる。
修正前のコード
// src/js/components/List.js import React from 'react'; import { connect } from 'react-redux'; const mapStateToProps = state => { return { articles: state.articles }; }; const ConnectedList = ({ articles }) => ( <ul> {articles.map(el => ( <li key={el.id}>{el.title}</li> ))} </ul> ); const List = connect(mapStateToProps)(ConnectedList); export default List;
修正後のコード
// src/js/components/List.js import React from 'react'; import { useSelector } from 'react-redux'; const ConnectedList = () => { const articles = useSelector(state => state.articles); return ( <ul> {articles.map(el => ( <li key={el.id}>{el.title}</li> ))} </ul> ); }; export default ConnectedList;
useSelector
でstate
からデータにアクセスすることによって、connect
が不要になるため、かなりすっきりした書き方ができる。
Action
がdispatch
されるたびにuseSelector
が実行される。(アクションでstateが書き換わるたびに、新しい値にアクセスする)Action
がdispatch
されると、useSelector
は結果と現在地の参照比較を行う。異なる場合にコンポーネントを再レンダリングする。
3. useDispatch
関数を使用して、dispatchを実行する
const dispatch = useDispatch()
useDispatch
関数を使用することで、コンポーネントはdispatchへの参照を得る。
connect
を使用していたコンポーネントをuseDispatch
にリファクタリングを行うと、下記のようになる。
修正前のコード
// 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;
修正後のコード
// src/js/components/Form.js import React, { useState } from 'react'; import { useDispatch } from 'react-redux'; import { ADD_ARTICLE } from '../constants/action-types'; const ConnectedFrom = props => { const [title, setTitle] = useState(''); const dispatch = useDispatch(); const handleChange = (event) => { setTitle(event.target.value); } const handleSubmit = (event) => { event.preventDefault(); dispatch({ type: ADD_ARTICLE, payload: { title } }); setTitle(''); }; return ( <form> <div> <label htmlFor="title">Title</label> <input type="text" id="title" value={title} onChange={handleChange} /> </div> <button onClick={handleSubmit}>SAVE</button> </form> ) }; export default ConnectedFrom;
変更点
- Classコンポーネントを関数コンポーネントに変更(ClassコンポーネントはHooksAPIを使用できないため)
useState
(React Hook)を使用して、初期値の設定useDispatch
関数を利用してdispatch
の実行
こちらもRedux Hooksを利用することで(Classコンポーネントから関数コンポーネントに変えた影響も大きいが)非常に見通しの良いコードになった。