0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reactにおける依存関係のあるState管理のベストプラクティス

Last updated at Posted at 2024-09-14

1. 概要

Reactを実装していると、複数のstateが相互に依存するシチュエーションに遭遇することがあります。このような場合、適切な方法でstateを管理しないと、コードが複雑になり、バグの温床になることがあります。ここでは、依存関係のあるstateを管理するベストプラクティスについて説明します。

2. 内容

2-1. Bad Code

依存関係のあるstateを独立して定義し、それらをuseEffectで更新する方法は推奨されません。
例えば、商品の値段と最終的な会計金額を扱う以下のようなコードがあったとします。

badCode.jsx
const PriceCalculator = ({ basePrice, taxRate }) => {
  const [totalPrice, setTotalPrice] = useState(0);
  const [finalPrice, setFinalPrice] = useState(0);

  useEffect(() => {
    setTotalPrice(basePrice * (1 + taxRate));
  }, [basePrice, taxRate]);

  useEffect(() => {
    // 10%のチャージを加算
    setFinalPrice(totalPrice + (totalPrice * 0.1)); 
  }, [totalPrice]);

  return (
    <div>
      <p>Base Price: {basePrice}</p>
      <p>Total Price: {totalPrice}</p>
      <p>Final Price: {finalPrice}</p>
    </div>
  );

このコードは以下の3つの問題点を抱えています。

  • 同期の問題
    • useEffectはレンダリング後に非同期的に実行されるため、依存関係のあるstate間での更新が即時に反映されない可能性があります。例えば、totalPriceが更新された後にfinalPriceが更新される場合、useEffectの順序によっては以前の値を元に計算されることがあり、意図しない値が表示されることがあります。
  • 冗長なコード
    • 複数のuseEffectフックを使用して依存関係のあるstateを管理すると、各useEffectがどのstateに依存しているかを個別に記述する必要が出てきます。これにより、依存関係が複雑になると、それぞれのuseEffect内で似たような更新ロジックを何度も書く羽目になります。例えば、totalPriceが変わるたびにfinalPriceを更新するようなロジックを複数のuseEffectで記述する必要が出てきます。これが原因でコードが冗長になり、どのuseEffectがどのstateを更新するのかが分かりにくくなります。
  • バグのリスク
    • stateを手動で同期させる際に、更新のタイミングや順序が予期しない形で影響を与えることがあります。例えば、setTotalPriceが呼ばれた後にsetFinalPriceが呼ばれる場合、totalPriceの更新が完了する前にfinalPriceの計算が始まることがあり得ます。その結果、finalPriceの計算が正しいtotalPriceの値に基づかず、古い値に基づいてしまうことがあります。これが原因で、正しい最終価格が表示されず、ロジックの整合性が崩れます。

2-2. Good Code

上述の問題に対し、useMemoを使うことで依存関係のあるstateを効率的に管理することができます。

goodCode.tsx
const PriceCalculator = ({ basePrice, taxRate }) => {
  const totalPrice = useMemo(() => {
    return basePrice * (1 + taxRate);
  }, [basePrice, taxRate]);

  const finalPrice = useMemo(() => {
    // 10%のチャージを加算
    return totalPrice + (totalPrice * 0.1); 
  }, [totalPrice]);

  return (
    <div>
      <p>Base Price: {basePrice}</p>
      <p>Total Price: {totalPrice}</p>
      <p>Final Price: {finalPrice}</p>
    </div>
  );
};

(問題点の裏返しにはなりますが)これにより以下の点で改善が期待されます。

  • 可読性の向上
    • サンプルコードとしてtotalPricefinalPriceという、あえて微妙な変数名をつけていますが、useMemoで定義することで、名前が本来の責務を表現できていなかったとしても、それらの算出ロジックが定義場所から読み取れるため、実装者がそれぞれの役割を推察しやすくなります。
  • 明確な依存関係
    • useMemoを使うことで、計算された値がどの依存関係によって更新されるかを明示的に管理できます。これにより、依存関係が変わったときのみ再計算が行われ、無駄な再レンダリングを避けることができます。
  • パフォーマンスの向上
    • useMemoは、依存している値が変更された場合のみ再計算を行うため、パフォーマンスが向上します。必要なときにのみ計算が行われることで、不要な計算を避け、アプリケーション全体のレスポンスが向上します。

3. まとめ

useState,useEffectはreactを代表するhooksである一方で、使い方によってはコードの複雑性をあげバグの温床にもなり得るものなので、気軽に定義せず、本当に必要なのか一考することをお勧めします。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?