⚙️

React で初期化時に 1 回だけ処理を実行したいときの書き方

に公開

現時点では React でクラス コンポーネントを書くことは稀になっています。クラス コンポーネントにはマウント時に発生する componentDidMount がありましたが、関数コンポーネントではこのメソッドはありません。ではどのように実装するかというと、useEffect を使います。

function HelloWorldComponent() {

  // これはコンポーネントの初期化時にのみ実行されます
  // 依存関係に空の配列を指定する必要があります
  React.useEffect(() => {
    console.log('Hello world!');
  }, []);

  return (
    <div>Hello world!</div>
  );

}

実際には初期化時に外部の API やデータ ストアへのアクセスなどのロジックを含めることが多いです。これらはカスタム フックなどを経由して実行することが一般的です。上記のコードの console.log メソッドを何かしらのサービスの呼び出しに書き換えてみます。

function HelloWorldComponent() {

  // 何かしらのサービスを提供するカスタムフック
  const someService = useSomeService();

  // コンポーネントの初期化時にのみサービスを呼び出します
  React.useEffect(() => {
    someService.run('Hello world!');
  }, []);

  return (
    <div>Hello world!</div>
  );

}

この実装でも動作しますが、ESLint のルール (react-hooks/exhaustive-deps) に引っかかります。依存関係を指定していないためです。そこで修正します。

function HelloWorldComponent() {

  // 何かしらのサービスを提供するカスタムフック
  const someService = useSomeService();

  // コンポーネントの初期化時にのみサービスを呼び出します
  // ESLint に警告されないように依存関係を追加します
  React.useEffect(() => {
    someService.run('Hello world!');
  }, [ someService ]);

  return (
    <div>Hello world!</div>
  );

}

この方法でほとんどの場合は期待通りに動作します。ただし、someService の値は不変であることが前提です。依存関係にある変数の値が意図せず更新されることで useEffect が複数回呼ばれてしまうことがあります。このパターンの場合はカスタム フックの設計を見直すべきですが、呼び出し側で対応が必要な場合もあります。複数回の呼び出しを防ぐためにフラグを用意します。

function HelloWorldComponent() {

  // 初期化を検知するフラグ
  const [ loading, setLoading ] = React.useState(true);
  // 何かしらのサービスを提供するカスタムフック
  const someService = useSomeService();

  // コンポーネントの初期化時にのみサービスを呼び出します
  React.useEffect(() => {
    // すでに初期化されていた場合は処理を抜けます
    if (!loading) {
      return;
    }
    someService.run('Hello world!');
    // 初期化済みのフラグを立てます
    setLoading(false);
   }, [ loading, someService ]);

  return (
    <div>Hello world!</div>
  );

}

このようにすることで、初期化時に 1 回のみ実行されるようにできます。

おわりに

初期化時に一度だけ処理を実行したつもりでも、実際には複数回呼び出されてしまうケースは少なくありません。特に開発者ツールなどで API の呼び出し回数を確認すると、意図しない挙動に気付くことがあります。 React の useEffect では依存関係やカスタムフックの設計に注意し、必要に応じてフラグなどで制御することが重要です。

Discussion