ReduxでuseSelectorとuseDispatchを短いコードで覚える | React チュートリアル

<img class="aligncenter size-full wp-image-13745" src="https://off.tokyo/wp-content/uploads/2021/04/Redux.png" alt="" width="1200" height="600" />

[ソース : ReduxでuseSelectorとuseDispatchを短いコードで覚える | React チュートリアル | off.tokyo](https://off.tokyo/blog/useselector-usedispatch/)

&nbsp;

React Reduxは、既存のconnect()高次コンポーネントの代替としてHooksのセットを提供しています。

&nbsp;

これらのHookは、connect()でコンポーネントをラップすることなく、Reduxストアへの接続やアクションのディスパッチを可能にします。

&nbsp;

このガイドでは、React-Redux HooksであるuseSelectorとuseDispatchをアプリケーションに実装する方法を説明します。

&nbsp;

<img class="aligncenter size-full wp-image-13915" src="https://off.tokyo/wp-content/uploads/2021/05/1_Sr7axcFaMxvbbyyiP4w2OQ.gif" alt="" width="892" height="368" />

カウンターとユーザーのログイン状態を表示する完成形があることを考えてみてください。

&nbsp;

カウンターを記録するための状態と、ログインしたユーザーを記録するための状態の2つに分かれています。

&nbsp;

それぞれの状態を処理するために、別々のファイルを用意します。

&nbsp;
<h2>actions and reducersフォルダの作成</h2>
&nbsp;

<img class="aligncenter size-full wp-image-13917" src="https://off.tokyo/wp-content/uploads/2021/05/1_sAu3-713CQYMvofUQZChJg.png" alt="" width="382" height="538" />

&nbsp;
<h3>アクション</h3>
&nbsp;

まずは counterActions.js でカウンターのアクションを定義してみましょう。

&nbsp;

インクリメントとデクリメントという2つのメソッドが必要です。

&nbsp;

この2つのメソッドは、オブジェクトとしてエクスポートします。

&nbsp;
<h4>counterActions.js</h4>
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">const increment = () =&gt; {
    return {
        type: "INCREMENT"
    }
}

const decrement = () =&gt; {
    return {
        type: "DECREMENT"
    }
}

export default {
    increment,
    decrement
}</pre>
</div>
&nbsp;

同様に、userActions.jsで現在のユーザーに対するアクションを定義しましょう。

&nbsp;

ここにもsetUserとlogOutという2つのメソッドがあり、オブジェクトとしてエクスポートされます。

&nbsp;
<h4>userActions.js</h4>
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">const setUser = (userObj) =&gt; {
    return {
        type: "SET_USER",
        payload: userObj
    }
}

const logOut = () =&gt; {
    return {
        type: "LOG_OUT"
    }
}

export default {
    setUser,
    logOut
}</pre>
</div>
&nbsp;

これらの2つのファイルを1つの場所、つまりactionフォルダ内のindex.jsファイルにインポートします。

&nbsp;

変数allActionsを作成し、インポートしたアクションを含むオブジェクトを設定します。

&nbsp;
<h4>index.js</h4>
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">import counterActions from './counterActions'
import userActions from './userActions'

const allActions = {
    counterActions,
    userActions
}

export default allActions</pre>
</div>
&nbsp;
<h2 id="af9e" class="mb kz fp as la nm nn gp ld no np gs lg gt nq gv lk gw nr gy lo gz ns hb ls nt bx" data-selectable-paragraph="">レデューサ</h2>
&nbsp;

アクションのファイル構造と同様に、reducersフォルダを作成して、ユーザー用とカウンター用のリデューサを格納します。

&nbsp;

まずは、カウンターのリデューサであるcounter.jsから始めましょう。

レデューサ関数は、2つの引数、stateとactionを受け取りまして、stateは、必ずしもオブジェクトに設定する必要はありません。

&nbsp;

今回の場合、stateのデフォルト値は整数に設定されています。

&nbsp;

先に定義したように、アクションは2つのキー、タイプとオプションでペイロードを含むことができるオブジェクトを返します。

&nbsp;

アクションのタイプに応じて、stateの値が変更されるわけですが、アプリが壊れないように、存在しないアクションタイプが呼び出された場合には、デフォルトケースが必要であることを覚えておいてください。

&nbsp;
<h4>counter.js</h4>
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">const counter = (state = 1, action) =&gt; {
    switch(action.type){
        case "INCREMENT":
            return state + 1
        case "DECREMENT":
            return state - 1
        default: 
            return state
    }
}

export default counter</pre>
</div>
&nbsp;

カレントユーザーのリデューサであるcurrentUser.jsでは、キーuserとloggedInを含む空のオブジェクトがステートに設定されます。

&nbsp;

counterとcurrentUserでは、返される値が異なることに注目してください。

&nbsp;

<span style="color: #ff0000;">counterリデューサは、初期値が整数であるため、整数を返します、current userのリデューサの場合は、常にオブジェクトが返されます。</span>

&nbsp;
<h4>currentUser.js</h4>
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">const currentUser = (state = {}, action) =&gt; {
    switch(action.type){
        case "SET_USER":
            return {
                ...state,
                user: action.payload,
                loggedIn: true
            }
        case "LOG_OUT":
            return {
                ...state,
                user: {},
                loggedIn: false
            }
        default:
            return state
    }
}

export default currentUser;</pre>
</div>
&nbsp;

これらのレデューサーを1つにまとめる必要があります。

&nbsp;

reducers/index.jsの下で、reducerのファイルとcombineReducersをインポートしましょう。

&nbsp;
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">import {combineReducers} from 'redux'</pre>
</div>
&nbsp;
<h4>index.js</h4>
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">import currentUser from './currentUser'
import counter from './counter'
import {combineReducers} from 'redux'

const rootReducer = combineReducers({
    currentUser,
    counter
})

export default rootReducer</pre>
</div>
&nbsp;

Combine reducersは，その名のとおり，別々のリデューサファイルを1つにまとめます。

&nbsp;

引数は1つで、レデューサーファイルを格納したオブジェクトを受け取ります。

&nbsp;

アクションとリデューサの設定が完了したので、アプリへのReduxの実装に移りましょう。

&nbsp;
<h2>Reduxの実装</h2>
&nbsp;

srcフォルダの下にあるindex.jsでは、以下のようにインポートします。

&nbsp;

&nbsp;
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">import {Provider} from 'react-redux';
import {createStore} from 'redux'
import rootReducer from './reducers'</pre>
</div>
&nbsp;

&nbsp;

ReduxのストアはcreateStoreというメソッドで作成されます。

&nbsp;

このメソッドは2つの引数を取ります。

&nbsp;

rootReducerはレデューサーを組み合わせたファイルで、Reduxのdevtools拡張機能です。

&nbsp;
<h4>index.js</h4>
&nbsp;
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">import {createStore} from 'redux'
import rootReducer from './reducers'
import {Provider} from 'react-redux'

const store = createStore(
    rootReducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ &amp;&amp; window.__REDUX_DEVTOOLS_EXTENSION__() 
)

ReactDOM.render(&lt;Provider store={store}&gt;&lt;App /&gt;&lt;/Provider&gt;, document.getElementById('root'));
</pre>
</div>
&nbsp;

最後に、Appコンポーネントをreact-reduxのProviderコンポーネントでラップします。

&nbsp;

Providerコンポーネントは1つのpropであるstoreを受け取り、

&nbsp;

createStoreからのstoreに設定されます。

&nbsp;
<h2>useSelector/useDispatchの実装</h2>
&nbsp;

react-reduxから次のフック、useSelectorとuseDispatchをインポートします。

&nbsp;

以前は、react-reduxからconnect()をインポートして、ステートをpropsにマッピングしたり、

&nbsp;

ディスパッチをpropsにマッピングするために、コンポーネントをそれでラップする必要がありました。

&nbsp;
<h3>useSelector</h3>
&nbsp;

map state to props に相当するのが useSelector です。

&nbsp;

useSelectorは、stateの必要な部分を返す関数の引数を受け取ります。

&nbsp;

今回の例では、stateからcounterとcurrentUserというキーが定義されています。

&nbsp;

これらは先ほどreducersを組み合わせる際に定義しました。

&nbsp;
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">const counter = useSelector(state =&gt; state.counter)
// 1
const currentUser = useSelector(state =&gt; state.currentUser)
// {}</pre>
</div>
&nbsp;

そのため、変数counterとcurrentUserには、それぞれのリデューサから定義された状態が設定されます。

&nbsp;
<h3>useDispatch</h3>
&nbsp;

map dispatchのpropsへの対応に相当するのが、useDispatchです。

&nbsp;

useDispatchを起動して、変数dispatchに格納します。

&nbsp;

dispatchはactionフォルダからインポートされたallActionsと連動します。

&nbsp;

例えば、useEffectは次のようなアクション、allActions.userActions.setUser(user)でディスパッチを呼び出します。

&nbsp;

ユーザーは次のように定義されます。

&nbsp;
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">const user = {name: "Rei"}</pre>
</div>
&nbsp;

allActionsは、userActionsとcounterActionsをキーとしたオブジェクトです。

&nbsp;

userActions.jsで定義されているsetUser関数の再確認をしましょう。

&nbsp;
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">const setUser = (userObj) =&gt; {
   return {
      type: "SET_USER",
      payload: userObj
    }
}</pre>
</div>
&nbsp;

setUserは、タイプとペイロードを持つオブジェクトを返します。

&nbsp;

Dispatchはこのオブジェクトを受け取り、アクションタイプにマッチしたreducerを探します。

&nbsp;

この例では、reducersフォルダ内のcurrentUser.jsがそれにあたります。

&nbsp;
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">case "SET_USER":
   return {
   ...state,
   user: action.payload,
   loggedIn: true
}</pre>
</div>
&nbsp;
<h4>App.js</h4>
<div class="wp-block-codemirror-blocks code-block ">
<pre class="CodeMirror" data-setting="{&quot;mode&quot;:&quot;htmlmixed&quot;,&quot;mime&quot;:&quot;text/html&quot;,&quot;theme&quot;:&quot;material&quot;,&quot;lineNumbers&quot;:false,&quot;lineWrapping&quot;:false,&quot;styleActiveLine&quot;:false,&quot;readOnly&quot;:true,&quot;align&quot;:&quot;&quot;}">import React, {useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux'
import './App.css';
import allActions from './actions'


const App = () =&gt; {
  const counter = useSelector(state =&gt; state.counter)
  const currentUser = useSelector(state =&gt; state.currentUser)

  const dispatch = useDispatch()

  const user = {name: "Rei"}

  useEffect(() =&gt; {
    dispatch(allActions.userActions.setUser(user))
  }, [])

  return (
    &lt;div className="App"&gt;
      {
        currentUser.loggedIn ? 
        &lt;&gt;
          &lt;h1&gt;Hello, {currentUser.user.name}&lt;/h1&gt;
          &lt;button onClick={() =&gt; dispatch(allActions.userActions.logOut())}&gt;Logout&lt;/button&gt;
        &lt;/&gt; 
        : 
        &lt;&gt;
          &lt;h1&gt;Login&lt;/h1&gt;
          &lt;button onClick={() =&gt; dispatch(allActions.userActions.setUser(user))}&gt;Login as Rei&lt;/button&gt;
        &lt;/&gt;
        }
      &lt;h1&gt;Counter: {counter}&lt;/h1&gt;
      &lt;button onClick={() =&gt; dispatch(allActions.counterActions.increment())}&gt;Increase Counter&lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch(allActions.counterActions.decrement())}&gt;Decrease Counter&lt;/button&gt;
    &lt;/div&gt;
  );
}

export default App;</pre>
</div>
&nbsp;

これで完成です。

&nbsp;

React-ReduxのフックであるuseSelectorとuseDispatchがReact Appに実装されています。

&nbsp;

connect()を使った場合と比べて、コードがすっきりと整理されています。

&nbsp;

お読みいただきありがとうございました。

&nbsp;

この記事は英語の記事を要点だけ絞って分かりやすく翻訳したものです、全文で詳しく英語で読みたい方は下記へどうぞ。

&nbsp;

https://levelup.gitconnected.com/react-redux-hooks-useselector-and-usedispatch-f7d8c7f75cdd