React Redux - Hooks APIを使用してReactコンポーネントとReduxストアを接続させる

やりたいこと

  • ReduxのHooksAPIを使用してconnect()を使用せずにReactからReduxに接続する

公式ドキュメント
react-redux.js.org

下記チュートリアルのソースをベースにリファクタリングを行う
www.valentinog.com

初めに(注意点)

Hooks API(useSelectoruseDispatch)は関数コンポーネントでのみ機能します。
そのため、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をコンポーネント全体で利用できるようにする。

参考 Provider · React Redux

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;

useSelectorstateからデータにアクセスすることによって、connectが不要になるため、かなりすっきりした書き方ができる。

  • ActiondispatchされるたびにuseSelectorが実行される。(アクションでstateが書き換わるたびに、新しい値にアクセスする)
  • Actiondispatchされると、useSelectorは結果と現在地の参照比較を行う。異なる場合にコンポーネントを再レンダリングする。

詳細 Hooks - useSelector() · React Redux

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;

変更点

こちらもRedux Hooksを利用することで(Classコンポーネントから関数コンポーネントに変えた影響も大きいが)非常に見通しの良いコードになった。

参考 Hooks - useDispatch() · React Redux