💻

ReactのuseStateの引数を無名関数にした方がいいとき

2024/02/29に公開

結論

  • valueみたいな変数を参照して何らかの判定を行う時:無名関数
    • [foo, changeFoo] = useState<boolean>(() => value === 'sample')
  • 1とかtrueみたいなプリミティブな値の時:そのまま渡す
    • [foo, changeFoo] = useState<boolean>(true)

なんで?

  • パフォーマンスが改善されるから
    • 無名関数にすると、コンポーネントがマウントされてから一度だけ無名関数が実行される
    • 変数を参照しているのに無名関数にしていない場合、コンポーネントが再レンダリングされる度に引数の判定が何度も実行される!
    • プリミティブな値(1とかtrue)を渡す場合は判定不要なので、そのまま渡せばOK

修正の具体例

  • 修正前と修正後で挙動そのものは変わらないが、パフォーマンスが改善される

Before

  • コンポーネントが再レンダリングされる度にvisualRef!== undefinedの判定が実行される
  • 機能としては問題ないが、パフォーマンスに改善の余地あり
const [isTransparent, changeIsTransparent] = useState<boolean>(visualRef!== undefined);

After

  • useStateの引数を無名関数化したことにより、コンポーネントがマウントされた時に一度だけvisualRef !== undefinedの判定が実行される
  • 無駄なレンダリングコストを削減できた!
const [isTransparent, changeIsTransparent] = useState<boolean>(() => visualRef !== undefined);

余談1: useMemoで初期値を設定するのと同じ

  • ちなみに、↑は以下の内容と等価
    • useMemoでメモ化して、不要な再計算が行われないようにしている
    • これならuseState + 無名関数にリファクタした方が可読性が高い
const hasVisualRef = useMemo(() => visualRef !== undefined);
const [isTransparent, changeIsTransparent] = useState<boolean>(hasVisualRef);

余談2: useStateの初期値が複雑な場合、無名関数化すると可読性が高まる

  • 引数に応じて初期値を変える場合、こんな風に無名関数化すると読みやすい
  • この場合はレンダリングコストというより、コードをスッキリさせるために無名関数を使うとOK
const [status, changeStatus] = useState<'下書き' | '公開' | 'その他'>(() => {
  switch (status) {
    case 1:
      return '下書き';
    case 2:
      return '公開';
    default:
      return 'その他';
  }
});

さいごに

「期待する動作は同じだが、パフォーマンスに改善の余地あり」という修正、気づくのが難しい...

Discussion