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


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

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

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

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

```jsx
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 を使わない場合だと、以下のようになります。

```jsx
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 関数を指定します。

```jsx
const counterSelector = (state) => state.counter

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

## useDispatch() 

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

```jsx
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`を利用して、メモ化することをオススメします。親の再レンダリングによって子コンポーネントが不必要に再レンダリングされることを回避するためです。

```jsx
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](https://react-redux.js.org/api/hooks#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)](https://qiita.com/Ouvill/items/a76e9cbce569d01f2931)