React Docsの「You Might Not Need an Effect」をしっかり理解する

こんにちは、フロントエンドエンジニアの堀江(@nandemo_3_)です。

だいぶ時間がたってしましましたが、 Reactのドキュメントが一時、Twitterで話題になりました。

話題となったドキュメントはこちらです。

react.dev

タイトル「You Might Not Need an Effect」は、

人によって、「Effectは不要」や「非推奨」という言い回しをされており、 今まで多用していたEffectが使えなくなることに、驚きを隠せませんでした。

そして、ちゃんとドキュメントを読むと、語弊があるなと思いましたので、解説がてら紹介します。

You Might Not Need an Effectの意味とは

まず、冒頭の説明について。

こちらを要約すると、

外部システムが関係しない場合は、Effectを必要としない場合があります。 不必要なEffectを削除することで、コードを追跡しやすく、実行速度を上げ、エラーが少なくなります。 つまり、ReactではEffectを使用することは重要ですが、外部システムに関与しない場合は、不要なEffectを避けることが重要です。

となります。

場合によってEffectは不要ということなので、

「Effectは不要」や「非推奨」だけでは、人によって解釈が変わってしまうので、注意が必要ですね。

2つの要点

このドキュメントで、まず語られていることは、2つあります。

1つ目のケース

1つ目のケースは、レンダリング用にデータを変換するためにEffectは不要というものです。

ドキュメントでは、ステート単体で完結するフィルタリングや表示形式の変換などは、Effectを使用せずにレンダリング時に行うと良いと言っています。

例えば、propsで受け取ったlistを昇順にソートするサンプルコードがあるとします。

function SortList({ list }) {
  const [newList, setNewList] = useState([]);
  
  useEffect(() => {
    setNewList(list.sort((a,b) => a + b))
  }, [list]);

  // ...
}

Effectを使わず、このように書くことができます。

function SortList({ list }) {
  const newList = list.sort((a,b) => a + b)
  // ...
}

Effectはしばしば、初期表示の処理やステート更新時の処理を書くことがありますが、

  1. 旧ステートでレンダリング

  2. ReactがDOMを更新

  3. Effectを実行

  4. 再レンダリング

という順番で処理されるため、 1回目のレンダリングで処理をまとめることで、余計なレンダリングを抑えられます。

そのため、APIが絡まないステート単体の処理などは、Effectを使用せず、関数のトップに書くのが最適解ということが分かりました。

2つ目のケース

2つ目のケースは、ユーザーイベントを処理するためにEffectは必要ないというものです。

ドキュメントでは、商品を購入したときに通知を表示する場合、Effectを使うよりも、購入ボタンのクリックイベントハンドラーで処理する方が適切と言っています。

例えば、プロフィールの新規作成と変更ができるボタンがあり、そのイベントごとにスナックバーを表示するサンプルコードがあるとします。

function Profile({ firstName, lastName }) {
  const [profile, setProfile] = useState(null);
  const [showSnackBar, setShowSnackBar] = useState("");

  useEffect(() => {
    if (profile !== null) {
      showSnackBar(`Update Profile.`);
    } 
  }, [profile]);

  function handleNewClick() {
    setProfile({ firstName, lastName });
  }

  function handleEditClick() {
    setProfile({ firstName, lastName });
  }
  // ...
}

上記のソースは、profileがnullでない場合、常にスナックバーが表示されてしまう問題があります。

以下のように関数化することで、問題が解決されます。

function Profile({ firstName, lastName }) {
  const [profile, setProfile] = useState(null);
  const [showSnackBar, setShowSnackBar] = useState("");

  function updateProfile {
    setProfile({ firstName, lastName });
    showSnackBar(`Update Profile.`);
  }

  function handleNewClick() {
    updateProfile()
  }

  function handleEditClick() {
    updateProfile()
  }
  // ...
}

このサンプルコード以外にも、原文にはたくさんのユースケースがありますので、ぜひ一読ください。

Effectの問題

Effectには問題があるようです。

Effectは、レンダリング後に処理が実行されます。

つまり、Effectがあると必ず2回以上処理されるということです。これは、実行処理的に効率が悪いです。

例え遅くなかったとしても、コードを追加していったときに、何度もレンダリングされることによる弊害が生まれる可能性もあります。

また、Effectはクライアント側で処理されます。これは、特にSSRを使っている場合に問題となります。

部分的にはSSRとCSRが混在するため、非効率です。(もちろんクライアント側で処理させたいという意図がある場合は、問題ないと思います)

おそらくこのドキュメントの作成意図や背景には、こういった文脈があるのではないかと思いました。

まとめ

最後まで、読んでくださりありがとうございます。

自分は、Effectが非推奨と知った時は、え!使っちゃダメなの?過去から現在まで多用してたんだけどどうすればいいの!?と不安になりました。

しかし、そんなことはなく、適切にEffectを理解し、最適な場所で使うことで、リーダブルなコードになり、実行速度、品質が向上するという内容でした。

正直、ドキュメントのサンプルコードを見て当たり前だと感じつつも、過去に不必要にEffectを使っていたと思うので、自省の念を込めて記事を書きました。

そして、最も信頼できる情報源である原文を読んで、自分で判断することが大切だと思いました。 つまり、この記事も参考にする程度で留めておくことを、お勧めします。