React 17 アップデートフォーカス-useEffect のクリーンアップ関数のタイミング変更

作成者:カランカラン
💡

質問やフィードバックがありましたら、フォームからお願いします

本文は台湾華語で、ChatGPT で翻訳している記事なので、不確かな部分や間違いがあるかもしれません。ご了承ください

(このスクリーンショットは React 公式ブログから取得しました)

React 17 はすでに RC ステージに入っています。公式ブログの声明によれば、今回のリリースには大きな更新や新機能はありませんが、記事の中にはいくつか興味深いポイントがありますので、ここで皆さんに紹介します。

本記事では、すべての更新を詳しく説明するのではなく、記事内で特に注目すべきポイントを記録します。

event delegation のノード変更

React では、イベントリスナー <button onClick={}> を使用すると、React はすべてのイベントリスナーを document に配置します。これを Event Delegation と呼びます。この手法の利点は、大量のイベントリスナーによるパフォーマンス問題を回避できることです。React 17 以降、リスナーは document ではなく root element に配置されるようになります。React の管理下にあるため、新機能の追加も容易になります。例えば、ツイートで言及されている Replay Event のようにです。

Event Pooling 機能の削除

パフォーマンス向上のために、React 17 以前は Event Pooling 機能を使用していました。これは独自に実装された Synthetic Event のセットです。しかし、公式によれば、現代のブラウザではそのパフォーマンス改善は実際には限られており、一部の開発者に混乱を招くこともありました。例えば、e.target を他のコンポーネントに渡そうとすると、なんと null になってしまうことがあります。これは React がパフォーマンス向上のために行った設計によるものです。正しく使うためには、e.persist() を追加で呼び出して React が介入しないことを確認する必要があります。

React 17 以降、Event Pooling 機能はすべて削除されたため、e.persist() を追加で呼び出す必要もなくなりました。素晴らしいですね。いくつかの文献やチュートリアルでは React の Event Pooling 設計について強調しているかもしれませんが、今後はその説明を見直す必要があるかもしれません。

スターを付ける:useEffect のクリーンアップ関数のタイミング変更

これが今回のアップデートで最も重要な変更の一つです。

useEffect を使用する際、ほとんどの effect は画面の更新とは関係がないため、React は画面が更新された後に effect を実行します。

全体の流れはこのようになります:

コンポーネントの更新 → DOM で対応する変更 → 画面更新 → effect を実行

これにより、effect 内での計算が重く、DOM の描画に影響を与えることを避けられます。例えば:

const App = () => {
  useEffect(() => {
    longlongTask(); // 私はとても長くかかりますよ
    console.log('Hello World');
  })
  return <Component />
}

上記の流れにより、React は画面が更新された後に他の effect を実行することで、ユーザー体験を損なうことを避けています。

しかし、時には画面の更新前に effect を実行して画面の跳ね上がりを防ぎたい場合もあります。その場合は useLayoutEffect を使用できます。

以上は React 16 の期待される動作です。これを再確認しました。次にクリーンアップ部分について説明します。

React 16 では、useEffect の effect は画面更新後に実行されますが、クリーンアップ関数は画面更新前に実行されます。一般的にはこれに大きな影響はありませんが、大部分のクリーンアップ関数は unsubscribe、リスナーの解除、API のキャンセルなどに過ぎません。しかし、クリーンアップ関数が長時間かかる場合、画面の更新に影響を与えることがあります。

例えば、次のように書いた場合:

const App = () => {
  useEffect(() => {
    longlongTask(); // 私はとても長くかかりますよ、でも大丈夫、React は画面更新後に実行します
    console.log('Hello World');
    return () => {
      longlongTask(); // 画面は longlongTask() が終了するまでフリーズします(React 16 の場合)
    }
  })
  return <Component />
}

React 16 では、クリーンアップの実行タイミングは以下のようになっています(コンポーネント更新時):

コンポーネントの更新 → DOM で対応する変更 → クリーンアップ関数を実行 → 画面更新 → effect を実行

一方、React 17 以降、クリーンアップのタイミングも画面更新後に変更されました(コンポーネント更新時):

コンポーネントの更新 → DOM で対応する変更 → 画面更新 → クリーンアップ関数を実行effect を実行

もしコンポーネントがアンマウントされる場合:

React 16:コンポーネントの更新 → クリーンアップ関数を実行 → DOM で対応する変更 → 画面更新

React 17:コンポーネントの更新 → DOM で対応する変更 → 画面更新 → クリーンアップ関数を実行

クリーンアップ関数は useEffect の順序で実行される

React 17 は、クリーンアップ関数をエフェクトと同じ順序で、ツリー内の位置に従って実行します。以前は、この順序が時折異なることがありました。

クリーンアップ関数に順序依存の必要性がある状況がどのようなものかは分かりませんが、React 16 ではクリーンアップの実行タイミングが予期しない順序になることがあったようです。

この記事が役に立ったと思ったら、下のリンクからコーヒーを奢ってくれると嬉しいです ☕ 私の普通の一日が輝かしいものになります ✨

Buy me a coffee