見出し画像

⚛️ useReducerを活用する

Reactで開発しているときuseReducer使ってますか?
useStateでええやんけと思っていませんか?
結構便利に使えるのでいくつかTipsを紹介します

useReducerてそもそもなんや?

useReducerとはReactが提供するstate管理系hookの一番primitiveなやつです。多分。というのもこの記事を書いている時点では一般的に広く使われているuseStateは内部でuseReducerを使っています。
なのでuseStateを使っているプロジェクトでuseReducerを部分的に使っても余分なコードをimportしていることにはならないのです。

そして一般的な使い方はReact公式サイトにも書いてあるとおり、useStateよりも複雑なstate更新に使うのが適しています。

↑公式のコード例を拝借するとname, ageを持つobjectのageだけをincrementしたいといった場合はかなりuseReducerが適していますね。

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Unknown action: ' + action.type);
}

function Example() {
  const [profile, dispatch] = useReducer(reducer, { name: 'example', age: 1 })

  return (
    <div>
      <p>Name: {profile.name}</p>
      <p>Age: {profile.age}</p>
      <button type="button" onClick={() => dispatch('incremented_age')}>
        Age++
      </button>
    </div>
  )
}

しかしこのような場合だけではなく意外とシンプルなstateでもuseReducerの威力を発揮することができます。

一方向のstate変化で使える

もしstateが一度だけ変更されてそれ以降変化しない場合、useReducerの出番です。

例えば最初は折りたたまれているテキストブロックがあり、一度開いたら開きっぱなしというUIがあった場合以下のように書くことができます。

const [isCollapsed, open] = useReducer(() => false, true)

<div>
  <p className={isCollapsed ? 'collapsed' : ''}>Lorem ipsum dolor sit amet...</p>
  <button type="button" onClick={open}>Read more</button>
</div>

dispatchの代わりにopenという名前で関数を受け取っていますが、reducerは常にtrueを返すので初期値のときだけfalseでそれ以外はtrueという動作が可能です。

これのいいところ上げると

  • useStateと違ってstate変更でtrue/falseを間違えてセットすることがない

  • onClickなどに無名関数ではなくそのままstate変更関数を渡せる

などがあるかなと思います。実際結構読みやすいと思うのですがどうでしょうか。

トグルのstate変化で使える

一方向の例を見てトグルでも使えるかも、と思いませんでしたか?はい、その通りですstateを反転するだけでトグルになります。

const [isCollapsed, toggle] = useReducer((state) => !state, true)

<div>
  <p className={isCollapsed ? 'collapsed' : ''}>Lorem ipsum dolor sit amet...</p>
  <button type="button" onClick={toggle}>Read more</button>
</div>

これも同様にonClickに無名関数を渡さずに済むので読みやすさという点で良きかと思います。
今までトグルを強制したくてuseStateを内包したhookを自作していたのならもう必要ないかもしれません。

// これを作らなくても良さそう
function useToggle(initial) {
  const [state, setState] = useState(initial)
  const toggle = () => setState(!state)
  return [state, toggle]
}

もちろんincrementもいい感じに書ける

const [count, increment] = useReducer((count) => count + 1, 0)
// もしくは
const [count, increment] = useReducer((count) => ++count, 0)

ここで気をつけたいのは count++ のようにpostfix operatorにするとちゃんと動かないということです。

// ❌ これはincrementされない
const [count, increment] = useReducer((count) => count++, 0)

postfixの場合incrementする前にreturnされて、その後incrementが行われるのでstateが変更されません。

いかがでしたか!?

♻️ useReducer使おう