ReactのuseEffectで実現する効率的なクリーンアップ処理:ベストプラクティスガイド

ReactのuseEffectフックは、コンポーネントがライフサイクルに応じた処理を実行するための非常に強力なツールです。特に、クリーンアップ処理はアプリケーションの健全性を保つために不可欠です。クリーンアップ処理を正しく実装しないと、メモリリークや予期しない挙動が発生し、パフォーマンスが低下する可能性があります。本記事では、useEffect内でのクリーンアップ処理に焦点を当て、その必要性、適切な実装方法、具体例、そしてベストプラクティスについて詳しく解説します。これにより、Reactを使用した効率的で安定したアプリケーション開発のための知識を深めることができます。

目次

useEffectの基本構造


ReactのuseEffectフックは、関数コンポーネント内で副作用を管理するために使用されます。副作用には、データのフェッチ、サブスクリプションの設定、DOMの直接操作などが含まれます。

基本的な構文


以下はuseEffectの基本的な構文です:

useEffect(() => {
  // 副作用の処理
  return () => {
    // クリーンアップ処理(必要なら)
  };
}, [依存配列]);

構文の説明

  • 第一引数: 副作用を記述する関数を指定します。この関数はコンポーネントのレンダー後に実行されます。
  • 依存配列: 第二引数として依存配列を渡します。この配列内の値が変更されたときに、副作用が再実行されます。配列が空の場合、初回レンダー時のみに実行されます。

簡単な例


次の例は、コンポーネントがマウントされたときにメッセージをコンソールにログ出力し、アンマウント時にクリーンアップを行うものです:

import React, { useEffect } from 'react';

const ExampleComponent = () => {
  useEffect(() => {
    console.log('Component mounted');
    return () => {
      console.log('Component unmounted');
    };
  }, []); // 空の依存配列

  return <div>Hello, useEffect!</div>;
};

主なポイント

  • 依存配列がない場合: 副作用は毎回のレンダー後に実行されます。
  • 空の依存配列を渡した場合: 副作用は初回レンダー後に一度だけ実行されます。
  • 特定の値を指定した場合: 指定した値が変更されたときのみ副作用が再実行されます。

useEffectの基本構造を理解することは、クリーンアップ処理を適切に実装するための第一歩です。

クリーンアップ処理の必要性

クリーンアップ処理とは、コンポーネントがアンマウントされたり、useEffectの再実行時に不要なリソースやイベントリスナーを解放するための重要なステップです。これを適切に行わないと、アプリケーションのパフォーマンス低下や予期しない動作が発生する可能性があります。

クリーンアップが必要な場面


以下の状況でクリーンアップ処理が必要になります:

1. サブスクリプションやリスナーの解除


addEventListenerやWebSocket接続などのサブスクリプションを設定した場合、アンマウント時にこれらを解除しないと不要なイベント処理が続き、リソースが浪費されます。

2. タイマーやインターバルのクリア


setIntervalsetTimeoutで設定したタイマーは、クリーンアップしないと不要な処理が実行され続けます。

3. 非同期処理の中断


APIコールなどの非同期処理がある場合、コンポーネントがアンマウントされても処理が続くと、不要なデータ操作が発生し、エラーの原因になります。

クリーンアップ処理を行わないリスク

1. メモリリーク


不要なリスナーや参照が残ると、ガベージコレクションが適切に機能せず、メモリリークが発生する可能性があります。

2. パフォーマンス低下


不要なイベントリスナーやタイマーが実行されると、アプリケーション全体のパフォーマンスが低下します。

3. 予期しない挙動


解除されていないリスナーが意図しない操作を引き起こし、UIが不安定になることがあります。

クリーンアップ処理の役割


クリーンアップ処理は、リソースを効率的に管理し、Reactアプリケーションの健全性を保つための不可欠な手段です。適切なクリーンアップを実装することで、以下を実現できます:

  • アプリケーションの信頼性向上: 意図しない動作を防止。
  • 効率的なリソース管理: メモリリークを防ぎ、パフォーマンスを最適化。
  • コードの保守性向上: 問題のトラブルシューティングが容易になる。

クリーンアップの必要性を理解した上で、次に具体例を通じてその実装方法を解説していきます。

クリーンアップ処理の具体例

ReactのuseEffectフックを使用したクリーンアップ処理の実装例をいくつか紹介します。これにより、さまざまな状況で適切なクリーンアップを行う方法を学びます。

例1: イベントリスナーの解除


ウィンドウサイズの変更を監視するイベントリスナーを設定し、アンマウント時に解除する例です。

import React, { useEffect, useState } from 'react';

const WindowResizeTracker = () => {
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWindowWidth(window.innerWidth);

    // イベントリスナーを設定
    window.addEventListener('resize', handleResize);

    // クリーンアップ処理でリスナーを解除
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // 初回マウント時のみ実行

  return <p>Window width: {windowWidth}px</p>;
};

export default WindowResizeTracker;

例2: タイマーのクリア


一定時間ごとに更新されるコンポーネントで、タイマーをクリアする例です。

import React, { useEffect, useState } from 'react';

const TimerComponent = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount((prevCount) => prevCount + 1);
    }, 1000);

    // クリーンアップ処理でタイマーをクリア
    return () => {
      clearInterval(intervalId);
    };
  }, []); // 初回マウント時のみ実行

  return <p>Count: {count}</p>;
};

export default TimerComponent;

例3: 非同期処理の中断


APIコールを中断するために、AbortControllerを利用した例です。

import React, { useEffect, useState } from 'react';

const FetchDataComponent = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/data', { signal });
        const result = await response.json();
        setData(result);
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          console.error('Fetch error:', error);
        }
      }
    };

    fetchData();

    // クリーンアップ処理でAPIコールを中断
    return () => {
      controller.abort();
    };
  }, []); // 初回マウント時のみ実行

  return <pre>{JSON.stringify(data, null, 2)}</pre>;
};

export default FetchDataComponent;

例4: カスタムサブスクリプションの解除


WebSocketなどのカスタムサブスクリプションを管理する例です。

import React, { useEffect, useState } from 'react';

const WebSocketComponent = () => {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = new WebSocket('wss://example.com/socket');

    socket.onmessage = (event) => {
      setMessages((prevMessages) => [...prevMessages, event.data]);
    };

    // クリーンアップ処理でソケットを閉じる
    return () => {
      socket.close();
    };
  }, []); // 初回マウント時のみ実行

  return (
    <ul>
      {messages.map((message, index) => (
        <li key={index}>{message}</li>
      ))}
    </ul>
  );
};

export default WebSocketComponent;

まとめ

  • イベントリスナーやタイマー、非同期処理、サブスクリプションには、必ずクリーンアップ処理を実装する。
  • クリーンアップは、Reactのレンダーサイクルにおいてリソースを効率的に管理するための重要なステップです。

次のセクションでは、これらのクリーンアップ処理がメモリリークを防ぐためにどのように役立つかを詳しく解説します。

メモリリークを防ぐテクニック

Reactアプリケーションでは、適切にクリーンアップを行わないとメモリリークが発生し、アプリケーションのパフォーマンスが低下します。ここでは、useEffectフックでメモリリークを防ぐための実践的なテクニックを紹介します。

メモリリークとは


メモリリークとは、不要になったメモリが解放されずに蓄積されていく現象を指します。これにより、システムリソースが枯渇し、アプリケーションが遅くなる、またはクラッシュする原因となります。

よくあるメモリリークの原因

1. 未解除のイベントリスナー


コンポーネントがアンマウントされた後もリスナーが残る場合、不要なイベントが発火し続けます。

2. 未クリアのタイマー


setIntervalsetTimeoutが解除されず、不要な処理が実行され続けます。

3. 中断されない非同期処理


非同期処理がコンポーネントのアンマウント後も実行され、結果が適用されようとすることでエラーが発生します。

4. 保留中の参照


不要になった変数やオブジェクトがメモリに残ると、ガベージコレクターが正しく機能しません。

メモリリークを防ぐ方法

1. イベントリスナーの適切な解除


コンポーネントで設定したイベントリスナーは、useEffectのクリーンアップ関数で必ず解除します。

useEffect(() => {
  const handleScroll = () => {
    console.log('Scrolling...');
  };
  window.addEventListener('scroll', handleScroll);

  return () => {
    window.removeEventListener('scroll', handleScroll);
  };
}, []);

2. タイマーのクリア


setIntervalsetTimeoutを使用した場合は、クリーンアップ関数でタイマーをクリアします。

useEffect(() => {
  const intervalId = setInterval(() => {
    console.log('Interval running...');
  }, 1000);

  return () => {
    clearInterval(intervalId);
  };
}, []);

3. 非同期処理の中断


APIリクエストや非同期処理にはAbortControllerを使用し、クリーンアップ時にリクエストを中断します。

useEffect(() => {
  const controller = new AbortController();
  const fetchData = async () => {
    try {
      const response = await fetch('https://api.example.com/data', {
        signal: controller.signal,
      });
      const data = await response.json();
      console.log(data);
    } catch (err) {
      if (err.name === 'AbortError') {
        console.log('Fetch aborted');
      } else {
        console.error(err);
      }
    }
  };
  fetchData();

  return () => {
    controller.abort();
  };
}, []);

4. 依存配列の適切な設定


依存配列を正しく設定し、不要な再レンダーや再実行を防ぎます。

useEffect(() => {
  console.log('Effect runs only when count changes');
}, [count]);

5. サードパーティライブラリのリソース管理


サードパーティのAPIやライブラリを使用する場合も、リソースを確実に解放します。

useEffect(() => {
  const mapInstance = new MapLibrary.Map('map-container');

  return () => {
    mapInstance.destroy();
  };
}, []);

ベストプラクティス

  • 常にreturn内でクリーンアップ処理を記述する。
  • AbortControllerを活用し、非同期処理を安全に中断する。
  • 開発ツールやブラウザのデベロッパーツールを活用し、メモリリークがないか確認する。

まとめ


適切なクリーンアップ処理を実装することで、メモリリークを防ぎ、Reactアプリケーションのパフォーマンスを最適化できます。依存配列や非同期処理に注意しながら、リソースを効率的に管理することが重要です。次のセクションでは、依存配列とクリーンアップの関係について詳しく説明します。

クリーンアップと依存配列の関係

ReactのuseEffectフックでは、依存配列(dependency array)がクリーンアップ処理の実行タイミングに大きな影響を与えます。依存配列を正しく設定することは、副作用とクリーンアップを効率的に制御するために不可欠です。

依存配列の役割


依存配列は、useEffectが実行される条件を制御します。この配列内に指定された値が変更されたときのみ、副作用が再実行されます。

依存配列の設定方法

useEffect(() => {
  // 副作用処理
  return () => {
    // クリーンアップ処理
  };
}, [依存配列]);
  • 空の配列 []: 初回レンダー時のみに実行。
  • 特定の依存項目を指定: 指定した値が変更されたときにのみ再実行。
  • 依存配列を省略: 毎回のレンダー後に実行(非推奨)。

依存配列がクリーンアップに与える影響

1. 空の依存配列


クリーンアップ処理は、コンポーネントのアンマウント時にのみ実行されます。

useEffect(() => {
  const handleResize = () => console.log('Resize event');
  window.addEventListener('resize', handleResize);

  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, []); // 初回のみ実行

2. 特定の依存項目を指定


依存配列に指定された値が変更されるたびにクリーンアップ処理が実行され、新しい副作用が設定されます。

useEffect(() => {
  const id = setInterval(() => console.log('Interval running'), 1000);

  return () => {
    clearInterval(id); // 前のタイマーをクリア
  };
}, [count]); // countが変更されるたびに実行

3. 依存配列を省略


毎回のレンダー後に副作用が実行されるため、不要なクリーンアップや副作用の再実行が発生し、パフォーマンスの問題を引き起こす可能性があります。

useEffect(() => {
  console.log('Effect runs on every render');
  return () => {
    console.log('Cleanup runs on every render');
  };
}); // 非推奨

依存配列のベストプラクティス

1. 必要な依存項目を正確に指定する


useEffect内で参照されるすべての値を依存配列に含めることで、予期しない動作を防ぎます。

useEffect(() => {
  console.log(value);
}, [value]); // valueの変更を監視

2. 空の依存配列は慎重に使用する


空の依存配列を使う場合は、その副作用が本当に初回レンダー時にのみ必要であるかを確認します。

3. 非同期処理と組み合わせる場合はAbortControllerを活用


非同期処理では、クリーンアップ時に未完了の処理を中断することで、依存項目の変化に安全に対応できます。

useEffect(() => {
  const controller = new AbortController();
  const fetchData = async () => {
    try {
      const response = await fetch('https://api.example.com/data', {
        signal: controller.signal,
      });
      console.log(await response.json());
    } catch (err) {
      if (err.name === 'AbortError') {
        console.log('Fetch aborted');
      }
    }
  };

  fetchData();
  return () => controller.abort();
}, [query]); // queryが変わるたびに新しいリクエストを送信

依存配列の警告を解消する方法


Reactは、依存配列が適切に指定されていない場合に警告を表示します。これを解消するには以下の方法を検討します:

  • eslint-plugin-react-hooksを導入: 依存配列の設定漏れを防ぐ。
  • 不要な依存を回避: 必要に応じて値をuseRefで保持し、依存配列を軽量化。
const stableValue = useRef(initialValue);
useEffect(() => {
  console.log(stableValue.current);
}, []); // stableValueを依存配列に含める必要なし

まとめ


依存配列はuseEffectの実行タイミングを制御し、クリーンアップ処理の重要なトリガーとなります。正確な依存配列の設定により、副作用とクリーンアップの効率性と信頼性を向上させることが可能です。次のセクションでは、非同期処理のクリーンアップについてさらに詳しく解説します。

非同期処理のクリーンアップ

Reactで非同期処理を扱う際、適切なクリーンアップ処理を実装することが重要です。これにより、コンポーネントのアンマウント後も非同期タスクが実行され続けることを防ぎ、メモリリークや意図しない挙動を防ぎます。

非同期処理が引き起こす問題

1. アンマウント後の状態更新


非同期処理が完了した後に、アンマウントされたコンポーネントでsetStateが呼び出されると、エラーや予期しない動作が発生します。

2. 不必要なリソース消費


アンマウント後もAPIリクエストやタイマーが動作し続けると、システムリソースが無駄になります。

3. データ競合


依存配列内の値が変更された際、前の非同期処理が新しい処理と競合して不整合を引き起こす可能性があります。

非同期処理のクリーンアップ方法

1. `AbortController`を使ったリクエストの中断


AbortControllerを利用することで、不要になったAPIリクエストを中断できます。

useEffect(() => {
  const controller = new AbortController();
  const signal = controller.signal;

  const fetchData = async () => {
    try {
      const response = await fetch('https://api.example.com/data', { signal });
      const data = await response.json();
      console.log(data);
    } catch (err) {
      if (err.name === 'AbortError') {
        console.log('Fetch aborted');
      } else {
        console.error(err);
      }
    }
  };

  fetchData();

  return () => {
    controller.abort(); // クリーンアップ時に中断
  };
}, [dependency]); // dependencyが変わるたびに新しいリクエスト

2. フラグを利用した状態更新の制御


アンマウント後に状態を更新しないよう、フラグを設定します。

useEffect(() => {
  let isMounted = true;

  const fetchData = async () => {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    if (isMounted) {
      console.log(data);
    }
  };

  fetchData();

  return () => {
    isMounted = false; // アンマウント時にフラグを更新
  };
}, []);

3. 非同期処理のキャンセル(Promiseのカスタム実装)


非同期処理が未完了のままアンマウントされた場合に備え、キャンセル可能なPromiseを実装します。

const useCancelablePromise = () => {
  const promises = useRef([]);

  useEffect(() => {
    return () => {
      promises.current.forEach((cancel) => cancel());
    };
  }, []);

  const cancelablePromise = (promise) => {
    let isCanceled = false;

    const wrappedPromise = new Promise((resolve, reject) => {
      promise
        .then((result) => (isCanceled ? reject({ isCanceled: true }) : resolve(result)))
        .catch((error) => (isCanceled ? reject({ isCanceled: true }) : reject(error)));
    });

    promises.current.push(() => {
      isCanceled = true;
    });

    return wrappedPromise;
  };

  return cancelablePromise;
};

4. クリーンアップのタイミングに注意


依存配列を正確に設定することで、不要な再実行を防ぎます。以下は、依存値が変更された際に前の非同期処理を中断する例です:

useEffect(() => {
  const controller = new AbortController();

  const fetchData = async () => {
    try {
      const response = await fetch(`https://api.example.com/data?query=${query}`, {
        signal: controller.signal,
      });
      const data = await response.json();
      console.log(data);
    } catch (err) {
      if (err.name !== 'AbortError') {
        console.error(err);
      }
    }
  };

  fetchData();

  return () => {
    controller.abort(); // 前のリクエストを中断
  };
}, [query]); // queryが変更されるたびに実行

非同期処理におけるベストプラクティス

1. 常に中断可能な構造を意識する


非同期処理を中断する仕組みを組み込むことで、安全なコンポーネント管理が可能になります。

2. クリーンアップ関数を利用する


useEffectreturn内で、中断やリソース解放を確実に行います。

3. 複数の非同期処理を追跡する仕組みを構築


複数の非同期処理が絡む場合は、カスタムフックを活用し、状態管理を簡素化します。

まとめ


非同期処理を適切にクリーンアップすることは、Reactアプリケーションの信頼性とパフォーマンスを維持するために不可欠です。AbortControllerやフラグ管理などの手法を活用し、コンポーネントのライフサイクルと調和した設計を行いましょう。次のセクションでは、他のフックと組み合わせた場合のクリーンアップの注意点について解説します。

他のフックとの組み合わせでの注意点

Reactでは、useEffectを他のフックと組み合わせて使用することが一般的です。しかし、その際にはクリーンアップ処理を適切に設計しないと、予期しない挙動やパフォーマンスの問題を引き起こす可能性があります。このセクションでは、useEffectと他のフックの組み合わせにおける注意点を解説します。

1. `useState`との組み合わせ

注意点: アンマウント後の状態更新


コンポーネントがアンマウントされた後にsetStateを呼び出すと、エラーが発生することがあります。これを防ぐには、isMountedフラグを使用して状態更新を制御します。

import React, { useState, useEffect } from 'react';

const ExampleComponent = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    let isMounted = true;

    fetch('https://api.example.com/data')
      .then((response) => response.json())
      .then((result) => {
        if (isMounted) {
          setData(result);
        }
      });

    return () => {
      isMounted = false; // アンマウント時にフラグを更新
    };
  }, []);

  return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
};

2. `useReducer`との組み合わせ

注意点: 再描画頻度の管理


useReducerで状態を更新する場合、依存配列にリデューサーの値を含めると不必要な再実行が発生する可能性があります。リデューサーの処理が副作用に影響しないよう設計することが重要です。

const reducer = (state, action) => {
  switch (action.type) {
    case 'SET_DATA':
      return { ...state, data: action.payload };
    default:
      return state;
  }
};

const ExampleComponent = () => {
  const [state, dispatch] = useReducer(reducer, { data: null });

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then((response) => response.json())
      .then((data) => dispatch({ type: 'SET_DATA', payload: data }));

    // クリーンアップ不要(依存配列は空)
  }, []);

  return <div>{state.data ? JSON.stringify(state.data) : 'Loading...'}</div>;
};

3. `useRef`との組み合わせ

注意点: 安定した値の保持


useRefは、副作用の依存を減らすために便利です。リファレンスを利用して状態を追跡する場合、適切に初期化とクリーンアップを行う必要があります。

useEffect(() => {
  const intervalRef = useRef();

  intervalRef.current = setInterval(() => {
    console.log('Interval running...');
  }, 1000);

  return () => {
    clearInterval(intervalRef.current);
  };
}, []);

4. `useContext`との組み合わせ

注意点: グローバル状態の変更に伴う再実行


useContextを使用してグローバルな状態を管理する場合、依存配列に含めるべきかを慎重に判断します。頻繁な再実行を防ぐために、必要に応じてメモ化を活用します。

import React, { useContext, useEffect } from 'react';

const DataContext = React.createContext();

const ExampleComponent = () => {
  const data = useContext(DataContext);

  useEffect(() => {
    console.log('Context data changed:', data);
    return () => {
      console.log('Cleanup on context change');
    };
  }, [data]); // Contextの変更を監視

  return <div>{data}</div>;
};

5. `useMemo`や`useCallback`との組み合わせ

注意点: メモ化の適切な使用


計算コストの高い処理をメモ化して依存配列を最適化します。ただし、useMemouseCallbackで作成した値や関数が依存配列に含まれる場合、その再生成を防ぐために依存関係を正確に指定することが重要です。

const ExampleComponent = () => {
  const computeExpensiveValue = useCallback(() => {
    // 高コストな計算
  }, [dependency]);

  useEffect(() => {
    console.log('Computed value:', computeExpensiveValue());
  }, [computeExpensiveValue]); // メモ化された値を依存配列に含む

  return <div>Check the console</div>;
};

まとめ

  • useEffectと他のフックを組み合わせる際は、依存配列やクリーンアップ処理に注意を払う。
  • 状態管理フック(useStateuseReducer)では、アンマウント後の状態更新を防ぐ仕組みを取り入れる。
  • メモ化やリファレンスを適切に利用して、再実行のコストを最小化する。

次のセクションでは、演習問題と応用例を通じて、これらの概念をさらに深く学びます。

演習問題と応用例

ReactのuseEffectフックとクリーンアップ処理に関する知識を深めるため、以下の演習問題と応用例を試してみてください。これらを実践することで、より効果的にuseEffectを活用できるようになります。

演習問題

1. イベントリスナーの解除


問題: 次のコンポーネントは、スクロールイベントを監視してスクロール位置を表示します。しかし、アンマウント時にイベントリスナーを解除していません。このコードを修正し、クリーンアップ処理を追加してください。

import React, { useState, useEffect } from 'react';

const ScrollTracker = () => {
  const [scrollY, setScrollY] = useState(0);

  useEffect(() => {
    const handleScroll = () => setScrollY(window.scrollY);
    window.addEventListener('scroll', handleScroll);

    // クリーンアップを追加する
  }, []);

  return <p>Scroll position: {scrollY}px</p>;
};

export default ScrollTracker;

2. 非同期処理のキャンセル


問題: このコンポーネントは、データをAPIからフェッチして表示します。ただし、useEffectにクリーンアップ処理がありません。AbortControllerを使用して、フェッチ処理を安全に中断できるよう修正してください。

import React, { useState, useEffect } from 'react';

const DataFetcher = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then((response) => response.json())
      .then((result) => setData(result));

    // クリーンアップを追加する
  }, []);

  return <pre>{JSON.stringify(data, null, 2)}</pre>;
};

export default DataFetcher;

3. 複数の依存値を管理


問題: 以下のコンポーネントは、queryが変更されるたびに新しいデータをフェッチします。しかし、依存配列が正しく設定されていないため、意図しない再実行が発生します。依存配列を正しく設定して修正してください。

import React, { useState, useEffect } from 'react';

const SearchComponent = ({ query }) => {
  const [results, setResults] = useState([]);

  useEffect(() => {
    const fetchResults = async () => {
      const response = await fetch(`https://api.example.com/search?q=${query}`);
      const data = await response.json();
      setResults(data);
    };

    fetchResults();
  }, []); // 依存配列を修正する

  return (
    <ul>
      {results.map((result, index) => (
        <li key={index}>{result.name}</li>
      ))}
    </ul>
  );
};

export default SearchComponent;

応用例

1. WebSocketの接続とクリーンアップ


WebSocketを利用してリアルタイムデータを表示するコンポーネントを実装します。WebSocketの接続を設定し、アンマウント時にクリーンアップするコードを完成させてください。

import React, { useState, useEffect } from 'react';

const WebSocketComponent = () => {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = new WebSocket('wss://example.com/socket');

    socket.onmessage = (event) => {
      setMessages((prevMessages) => [...prevMessages, event.data]);
    };

    return () => {
      // WebSocketをクローズする
    };
  }, []);

  return (
    <ul>
      {messages.map((message, index) => (
        <li key={index}>{message}</li>
      ))}
    </ul>
  );
};

export default WebSocketComponent;

2. タイマーのクリーンアップ


1秒ごとにカウントを増加させるタイマー付きのコンポーネントを作成します。タイマーをクリアするクリーンアップ処理を実装してください。

import React, { useState, useEffect } from 'react';

const TimerComponent = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount((prevCount) => prevCount + 1);
    }, 1000);

    return () => {
      // タイマーをクリアする
    };
  }, []);

  return <p>Count: {count}</p>;
};

export default TimerComponent;

まとめ


演習問題と応用例を通して、useEffectとクリーンアップ処理の具体的な実装方法を理解できました。これらを実践することで、Reactアプリケーションのリソース管理やパフォーマンスを最適化するスキルを向上させることができます。次のセクションでは、この記事のまとめに入ります。

まとめ

本記事では、ReactのuseEffectフックを利用したクリーンアップ処理の重要性とその実践的な実装方法について解説しました。useEffectの基本構造や依存配列の役割、具体的なクリーンアップ処理の例から、メモリリークを防ぐためのテクニック、非同期処理の安全な中断、他のフックとの組み合わせ時の注意点まで幅広く取り上げました。さらに、演習問題や応用例を通じて、実践的なスキルを身につける機会を提供しました。

適切なクリーンアップ処理は、Reactアプリケーションのパフォーマンスと信頼性を大幅に向上させます。useEffectを正しく活用し、効率的で安定したコードを書くことを心がけましょう。

コメント

コメントする

目次