🙌

【React】1度だけ変更されるstateにはuseStateよりuseReducerを使う方が最適

2022/11/15に公開
4

useStateでしょ! という声が聞こえてきますね。
おっしゃる通りなんですが、それよりも実はそういう時はuseReducerの方がより適しているという話です。

はじめましてスペースマーケットでフロントエンドエンジニア兼リーダーをしています。

はじめに

useState / useReducerがなんぞや?という方は公式のリファレンスをご確認ください。

https://ja.reactjs.org/docs/hooks-reference.html#usestate

https://ja.reactjs.org/docs/hooks-reference.html#usereducer

1度だけ変更されるstateでいうと下記のようなイメージでしょうか?

const Hoge: FC = () => {
  const [finished, setFinished] = useState(false)
  const onClick = () => {
    setFinished(true)
  }

  if (finished) return <div>送信しました!</div>
  return (
    <div>
      <button onClick={onClick}>送信する</button>
    </div>
  )
}

例えば問い合わせ画面のような画面があり、送信ボタンを押すと問い合わせの画面が完了画面へ切り替わるようなイメージです。
今回はフォームなどなど諸々を省略していますがご容赦ください。

これをuseReducerで書き直してみます。

const Hoge: FC = () => {
  const [finished, updateFinished] = useReducer(() => true, false)
  const onClick = () => {
    updateFinished()
  }

  if (finished) return <div>送信しました!</div>
  return (
    <div>
      <button onClick={onClick}>送信する</button>
    </div>
  )
}

このようになります。
このようにすることで次のメリットが生まれます。

useReducerはstateの再更新をしないことを明示できる

結局これにつきます。
もう一度コードをみてください。

const [finished, setFinished] = useState(false)

本来この画面では「送信するを押したら完了表示になり元の画面へ切り替わらない」ことが正しいです。
しかしfinishedはこの画面の要件を満たせていません。
useStateを使う場合、finishedの値はsetFinishedの実行結果によって更新されます。
今回はかなり小さな構成であり、早々このような事が起きないと思います。
もっと複雑な構成である場合、意図しないタイミングでsetFinishedが実行されることでバグを埋め込む可能性があります。
そのような可能性が生まれないようにしておければそれに越したことはありません。

上記のような場合に使うべきがuseReducerです。

const [finished, updateFinished] = useReducer(() => true, false)

useReducerを使ったupdateFinishedを実行するとfinishedのstateはtrueへ更新されます。
useStateと異なる点は、 updateFinishedを何度実行してもfinishedはtrueにしかなりません。
もう一度falseに戻ることがないので、「このstateは再更新されることはない」ということを明示できます。
これで誤った実装によるバグが生まれる可能性がなくなります。

おわりに

とてもシンプルな話ですが、とりあえず仕様を満たせばいいわけではなくコードでいかに仕様を表現するか大事だと思います。
みなさんのプロダクトでも是非こちら活用してみてください!

GitHubで編集を提案
スペースマーケット Engineer Blog

Discussion

TakashiAiharaTakashiAihara

良い記事ありがとうございます!
端的に言うと

setFinished(false)

されないというのが利点ですね?

いままで「とりあえずuseState」でしたが、
「useReduserでメリットがないならuseState」くらいの意識のほうが良さそうですね!

disneyLadySangodisneyLadySango

コメントありがとうございます!
おっしゃる通りで、終わったものを再度元に戻すような実装が埋め込むことができないというのがメリットになります

いままで「とりあえずuseState」でしたが、
「useReduserでメリットがないならuseState」くらいの意識のほうが良さそうですね!

こんな記事を書いておいてなんですが組織の風土やメンバーのレベル感などで変わると思うので一概にはこうすべき!とは言えないのが正直なところです笑

このような記事に反響をいただけたことは、
「言われてみたらこんなのできるじゃん」
「結構いいじゃん!」
「どうなんだろうこれは、いまいちかも」
と色々な意見があった結果だと思いますので、この実装が本当に「いいものか」というと一概にそうは言えないと思います。(正直初見だと???となると思いますので・・・)

一番良い形は、「このコードの書き方が本当にいいのか」「今回の要件はこれでいいんだっけ?」というのをチームで議論しつつ納得した上で実装に落とし込むことだと思います
useState使うのか、useReducerの方がいいんじゃないか、reduxなどのライブラリ使った方がいいのか?と言ったお話のきっかけに使っていただければ嬉しく思います!

XU ZHONGWEIXU ZHONGWEI

Hoge Componentがunmountされるときには、finishedがfalseにリセットされることはありますか?

disneyLadySangodisneyLadySango

unmountされるタイミングでfnishedがfalseになることはありません
ただunmountしたあと再度mountした場合は、stateが初期化されるのでfinishedはfalseになります!