409
312

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-06-11

本日 (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)

409
312
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
409
312

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?