React:Effect とは

Effect を理解するためには次のロジックを押さえておく必要がある。

  • レンダーコード

    コンポーネントのトップレベルにあるもので、propsstate から画面に表示したい JSX を返す場所。レンダーコードは純粋関数*1

  • イベントハンドラ

    コンポーネント内にネストされた関数で、計算だけでなく実行する。特定のユーザアクションによってプログラムの状態を変更する(副作用を与える)。

上記だけでは十分でないシーンがある。例えば、サーバとの通信を維持するような処理は、純粋関数ではないのでレンダーコードへは書けないし、『クリックする』といった特定のイベントもない。

そこで使うのが Effect で、特定のイベントによってではなく、レンダー自体によって引き起こされる副作用を指定するためのもので、コミットの最後に画面が更新された後に実行される。

Effect は React 外の外部システムとの同期が必要な場合*2において使用されるものと覚えておけば良さそうで、そうしたシーン以外では不要。

Effect の書き方

Effect は大きく3つのフローからなる。

  1. Effect を宣言する
  2. Effect の依存値の配列を指定する
  3. 必要に応じてクリーンアップを追加する

Effect を宣言する

まずは useEffect をインポートする:

import { useEffect } from "react";

次に、コンポーネントのトップレベルで呼び出し、Effect 内にコードを記述する:

function MyComponent() {
  useEffect(() => {
    // Recat はレンダーされた後に、Effect 内の処理を実行する
    // レンダーされる度にやってほしい処理を書く
  });
  return <div />;
}

Effect で囲わない場合、レンダー中に実行されることになる。レンダーコードは純粋関数である必要があり、副作用があるようなものは記述できない。

そこで、useEffect でラップし、レンダーの計算処理の外に出してしまう。そうすることで、React は Effect の処理はレンダー後に実行すべき処理と認識し、レンダーが完了した後に実行するようにできる。

Effect の依存値の配列を指定する

デフォルトでは、Effect は全てのレンダー後に実行されるが、望ましくない場合がある(キーストロークごとのレンダー後に何かを実行するなど)。

そこで、useEffect の呼び出しの第2引数に依存値の配列を渡し、React に再実行不要な Effect を指示することができる:

useEffect(() => {
  // ...
}, []);

Effect の処理が依存する変数を配列内に定義することで、渡した変数が変化するときのみ Effect の処理が実行するように React に教えることができる。

useEffect(() => {
  if (isPlaying) {
    // 依存変数はここで使われている
    // ...
  } else {
    // ...
  }
}, [isPlaying]); // Effect が依存する変数を書く

React は配列内の全ての値が前回レンダー時と同じ場合のみ Effect の処理をスキップする。

必要に応じてクリーンアップを追加する

クリーンアップ関数は、次のタイミングで React が呼び出す関数:

  • Effect が再度実行される前
  • コンポーネントがアンマウント(削除) される時の最後

書き方は次のように return でクリーン関数を返すようにする:

  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, []);

上記コードにおいて クリーンアップが無い場合、再マウント時に connection.connect() が呼ばれ2つ目の接続などが実行されてしまう。

それをクリーンアップ関数を用いることで、一度切断してから接続というフローを取らせることができるようになる。

ちなみに React ではそうした不具合を見つけやすくるために開発環境においては、初回マウント直後に全てのコンポーネントを一度だけ再マウントし気づきやすくしているらしい👀

参考

*1:同じ入力に対して常に同じ出力(副作用を与えない)

*2:ブラウザ APIサードパーティウィジェット、ネットワーク等