magazine off

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

投稿日 : 2021年5月4日(火曜日)

 

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

 

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

 

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

 

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

 

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

 

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

 

actions and reducersフォルダの作成

 

 

アクション

 

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

 

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

 

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

 

counterActions.js

const increment = () => {
    return {
        type: "INCREMENT"
    }
}

const decrement = () => {
    return {
        type: "DECREMENT"
    }
}

export default {
    increment,
    decrement
}

 

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

 

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

 

userActions.js

const setUser = (userObj) => {
    return {
        type: "SET_USER",
        payload: userObj
    }
}

const logOut = () => {
    return {
        type: "LOG_OUT"
    }
}

export default {
    setUser,
    logOut
}

 

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

 

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

 

index.js

import counterActions from './counterActions'
import userActions from './userActions'

const allActions = {
    counterActions,
    userActions
}

export default allActions

 

レデューサ

 

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

 

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

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

 

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

 

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

 

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

 

counter.js

const counter = (state = 1, action) => {
    switch(action.type){
        case "INCREMENT":
            return state + 1
        case "DECREMENT":
            return state - 1
        default: 
            return state
    }
}

export default counter

 

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

 

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

 

counterリデューサは、初期値が整数であるため、整数を返します、current userのリデューサの場合は、常にオブジェクトが返されます。

 

currentUser.js

const currentUser = (state = {}, action) => {
    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;

 

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

 

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

 

import {combineReducers} from 'redux'

 

index.js

import currentUser from './currentUser'
import counter from './counter'
import {combineReducers} from 'redux'

const rootReducer = combineReducers({
    currentUser,
    counter
})

export default rootReducer

 

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

 

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

 

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

 

Reduxの実装

 

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

 

 

import {Provider} from 'react-redux';
import {createStore} from 'redux'
import rootReducer from './reducers'

 

 

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

 

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

 

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

 

index.js

 

import {createStore} from 'redux'
import rootReducer from './reducers'
import {Provider} from 'react-redux'

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

ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));

 

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

 

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

 

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

 

useSelector/useDispatchの実装

 

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

 

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

 

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

 

useSelector

 

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

 

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

 

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

 

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

 

const counter = useSelector(state => state.counter)
// 1
const currentUser = useSelector(state => state.currentUser)
// {}

 

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

 

useDispatch

 

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

 

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

 

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

 

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

 

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

 

const user = {name: "Rei"}

 

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

 

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

 

const setUser = (userObj) => {
   return {
      type: "SET_USER",
      payload: userObj
    }
}

 

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

 

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

 

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

 

case "SET_USER":
   return {
   ...state,
   user: action.payload,
   loggedIn: true
}

 

App.js

import React, {useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux'
import './App.css';
import allActions from './actions'


const App = () => {
  const counter = useSelector(state => state.counter)
  const currentUser = useSelector(state => state.currentUser)

  const dispatch = useDispatch()

  const user = {name: "Rei"}

  useEffect(() => {
    dispatch(allActions.userActions.setUser(user))
  }, [])

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

export default App;

 

これで完成です。

 

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

 

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

 

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

 

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

 

A guide to using Redux with React hooks for cleaner code
React-Redux Hooks: useSelector and useDispatch - Medium

Categories

Recent Posts

How to cheat at gossip movies and get away with it

 How to cheat at money saving tips and ge…

投稿日 : 2015年12月20日(日曜日)

Swift3でUIViewをViewの中心に配置する方法

  実装 class ViewController…

投稿日 : 2017年5月27日(土曜日)

cssにstyle.css以外の名前をつけたら動かなくなった

    外部cssのファイル名というのには慣習的にstyle…

投稿日 : 2017年7月14日(金曜日)

herokuにデプロイしたらApp crashedでH10 したらこれやる

   herokuにデプロイしたらApp crashedでH10 した…

投稿日 : 2018年11月23日(金曜日)