Help us understand the problem. What is going on with this article?

Redux Hooks によるラクラク dispatch & states

本日 (2019/06/11) React-Redux の version 7.1 がリリースされて、 Redux Hooks が使用できるようになりました。

Redux Hooks によって、connect() を利用しなくても、各コンポーネントとdispatch, state が簡便に利用できるようになります。

サンプルコードです。Action, Reducer 部分は省略です。

ただシンプルにカウンターの数値を増減させるだけのコンポーネントです。

import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { incrementAction, decrementAction } from "./Actions";

const counterSelector = state => state.counter;

const Counter = props => {
  // useDispatch で store に紐付いた dispatch が取得できます。
  const dispatch = useDispatch();

  // useSelector で store の state が取得できます。
  const counter = useSelector(counterSelector);

  return (
    <>
      <p> count: {counter} </p>
      <button onClick={dispatch(incrementAction())}>increment</button>
      <button onClick={dispatch(decrementAction())}>decrement</button>
    </>
  );
};

useSelector と useDispatch を使わない場合だと、以下のようになります。

import React from "react";
import { connect } from 'react-redux'
import { incrementAction, decrementAction } from "./Actions";

const counterSelector = state => state.counter;

const CounterComponents = props => {
  return (
    <>
      <p> count: {props.counter} </p>
      <button onClick={props.increment}>increment</button>
      <button onClick={props.decrement}>decrement</button>
    </>
  );
};

const mapStateToProps = state => ({
  counter: counterSelector(state)
});

const mapDispatchToProps = dispatch => ({
  increment: () => dispatch(incrementAction()),
  decrement: () => dispatch(decrementAction())
});

const Counter = connect(
  mapStateToProps,
  mapDispatchToProps
)(CounterComponents);

Redux Hooks を利用すると connect 部分の記述がなくなり、かなりスッキリしたコードになるのがわかると思います。

useSelector()

store から state を取得する場合、useSelector() を呼びます。useSelector の引数には state を引数にとる selector 関数を指定します。

const counterSelector = (state) => state.counter

const Counter = (props) => {
    const counter = useSelector(counterSelector)
    return <div>{counter}</div>
}

useDispatch()

useDispatch で store に紐付いた dispatch が取得できます。

const Counter = (props) => {
    const dispatch = useDispatch()

    return (
        <>
            <button onClick={()=> {dispatch({ type: "INCREMENT_COUNTER" })}}>increment</button>
            <button onClick={()=> {dispatch({ type: "DECREMENT_COUNTER" })}}>decrement</button>
        </>
    )
}

子コンポーネントに dispatch を渡す場合、useCallbackを利用して、メモ化することをオススメします。親の再レンダリングによって子コンポーネントが不必要に再レンダリングされることを回避するためです。

import React, { useCallback } from 'react'
import { useDispatch } from 'react-redux'

export const CounterComponent = ({ value }) => {
  const dispatch = useDispatch()
  const incrementCounter = useCallback(
    () => dispatch({ type: 'increment-counter' }),
    [dispatch]
  )

  return (
    <div>
      <span>{value}</span>
      <MyIncrementButton onIncrement={incrementCounter} />
    </div>
  )
}

export const MyIncrementButton = React.memo(({ onIncrement }) => (
  <button onClick={onIncrement}>Increment counter</button>
))

注意点

mapStateToProps を利用する場合と異なり、useSelectorには若干の問題が含まれています。

とくに useSelector()props に依存するとき問題が発生しえます。参考:Usage Warnings

dispatchが実行されるたびに、useSelector() は実行されます。このとき、props が最新の状態である保証がありません。

  • dispatch() 実行され、コンポーネントの useSelector() が実行される。
  • 親のコンポーネントが再レンダリングされ、props がコンポーネントに渡される。
  • props が渡される前に、useSelector() が実行されているため、selector 関数がprops に依存していた場合、不整合が生じる。

対策としては以下のとおりです。

  • selector 関数では props を利用しない。
  • selector 関数が props に依存していて、状態変化に基づいて props が変化したり、state のデータが削除される場合、慎重に利用してください。state.todos[props.id].name などというように利用するのではなく、まずstate.todos[props.id] でデータの存在を確認してから、そのあとにtodo.name を呼び出してください。

所感

useContext と useReducer の組み合わせによって、簡易的なグローバル state を用意する方法は React Hooks が発表されてから提案されていました。

それと同じくらい簡便に Redux が利用できるのはかなり魅力的だと思います。

v7.1.0-alpha.5 のころから、ちょこちょこ利用してみましたが、なかなか便利です。

……やや取扱に注意が必要ですけど。

まとめ

以上、React Redux 7.1 から正式リリースとなった Redux Hooks についてでした。

こうした方がいいという知見がありましたら、コメントのほうに書いていただければ嬉しいです。

react-starter-kit を用いれば、更にコードの記述量が減らせて開発が楽になります。こちらも紹介記事を書きました。

Redux の記述量多すぎなので、 Redux の公式ツールでとことん楽をする。そしてデバッグはやりやすく ( redux-starter-kit)

Ouvill
プログラムとか小説とか書いている人。Web 開発に関するお仕事募集中です。というか誰か雇ってくださいませんか?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした