ReactのuseEffectで学ぶ状態変更時の副作用処理の基本と応用

Reactアプリケーションでは、状態管理と副作用処理がアプリの動作に大きく影響します。その中でも、useEffectはReactが提供する強力なフックの一つで、特定の状態やプロパティが変更された際に副作用を管理する役割を担っています。適切に活用することで、クリーンで効率的なコードを書くことができますが、誤った使い方をするとパフォーマンスの低下やバグの原因にもなります。本記事では、useEffectの基本から、実際の応用例やトラブルシューティングまでを解説し、Reactでの開発効率を向上させる方法を学びます。

目次

useEffectとは?基本的な仕組みの解説


ReactのuseEffectは、関数コンポーネントにおいて副作用を管理するためのフックです。副作用とは、コンポーネントのレンダリング以外で行われる操作を指します。たとえば、以下のようなケースが該当します:

副作用の具体例

  • データの取得(APIコール)
  • DOMの操作
  • サブスクリプションの設定(WebSocketやイベントリスナーなど)
  • タイマーの設定

useEffectは、コンポーネントのレンダリング後に副作用を実行し、必要に応じてクリーンアップ処理を行うこともできます。

基本的な使い方


以下のコードは、基本的なuseEffectの例です:

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

function ExampleComponent() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        console.log(`Count has changed to: ${count}`);
    });

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
    );
}

この例では、countが変更されるたびにuseEffectの中身が実行されます。

useEffectの基本的な動作

  • 初回レンダリング後に実行される。
  • 指定した状態やプロパティが変更されたときに再実行される。
  • 必要に応じてクリーンアップ処理を実行できる。

この基本動作を理解することで、useEffectを適切に活用できるようになります。次に、状態変更時における副作用の流れを詳しく見ていきます。

状態変更時の副作用処理の流れ

ReactのuseEffectを使用すると、状態変更時に特定の処理を実行できます。これにより、ユーザーインタラクションや外部データの変化に応じた柔軟な振る舞いをコンポーネントに与えることができます。

状態変更時に`useEffect`が実行されるタイミング

useEffectは、コンポーネントが最初にレンダリングされた後、または依存する状態やプロパティが更新されたときに呼び出されます。その具体的な流れは次のようになります:

1. 初回レンダリング時の実行


useEffectはコンポーネントの初回レンダリング後に一度実行されます。このタイミングで、データの取得や初期化処理を行うことができます。

2. 状態やプロパティの変更検知


useEffect内で依存する値(ディペンデンシー配列で指定)に変更があると、再度実行されます。たとえば、countという状態に依存している場合、countが更新されるたびにuseEffectが呼び出されます。

3. 再レンダリング後の実行


状態やプロパティの変更が検知されると、Reactはそのコンポーネントを再レンダリングします。その後、useEffect内の処理が実行されます。

具体例:状態変更時の副作用

以下は、状態変更によってデータが取得される例です:

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

function DataFetcher() {
    const [data, setData] = useState(null);
    const [query, setQuery] = useState('react');

    useEffect(() => {
        async function fetchData() {
            const response = await fetch(`https://api.example.com/search?q=${query}`);
            const result = await response.json();
            setData(result);
        }

        fetchData();
    }, [query]); // queryが変更されたときのみ実行

    return (
        <div>
            <input
                value={query}
                onChange={(e) => setQuery(e.target.value)}
                placeholder="Enter search query"
            />
            <pre>{JSON.stringify(data, null, 2)}</pre>
        </div>
    );
}

注意点

  1. 無限ループに注意
    副作用処理が状態を更新し、それが再びuseEffectをトリガーすると無限ループが発生します。依存関係を適切に管理しましょう。
  2. 適切な依存関係の指定
    useEffect内で使用している変数や状態を依存配列に含めないと、期待通りに動作しない場合があります。

状態変更と副作用の流れを理解することで、useEffectをより効果的に活用できます。次は、この流れをさらに制御するためのディペンデンシー配列について説明します。

ディペンデンシー配列の重要性

useEffectの第二引数であるディペンデンシー配列は、状態変更時の副作用処理を効率的に制御するための重要な要素です。この配列を適切に指定することで、useEffectが実行されるタイミングを明確に制御できます。

ディペンデンシー配列とは?

ディペンデンシー配列は、useEffectが依存する値のリストを指定するために使用されます。この配列に指定した値が変更された場合にのみ、useEffect内の処理が実行されます。

useEffect(() => {
    // 副作用処理
}, [dependency1, dependency2]);

上記の例では、dependency1dependency2が変更された場合にuseEffectが実行されます。

ディペンデンシー配列の使い方

  1. 配列を空にする
    配列を空にすることで、useEffectはコンポーネントの初回レンダリング時に一度だけ実行されます。
   useEffect(() => {
       console.log('This runs only once after the first render');
   }, []);
  1. 特定の依存関係を指定する
    配列内に指定した値が変更されたときにのみ、useEffectが再実行されます。
   useEffect(() => {
       console.log(`Dependency changed: ${dependency}`);
   }, [dependency]);
  1. ディペンデンシー配列を省略する
    ディペンデンシー配列を省略すると、useEffectはすべてのレンダリング後に実行されます。この方法は非推奨であり、パフォーマンス問題を引き起こす可能性があります。
   useEffect(() => {
       console.log('This runs after every render');
   });

配列管理の注意点

  • 配列内に正確な依存関係を指定する
    useEffect内で参照されるすべての状態やプロパティを正しく配列に含める必要があります。これを怠ると、予期しない動作を引き起こす可能性があります。
  • 無限ループを避ける
    配列内の依存関係が常に変化すると、無限ループが発生する可能性があります。副作用処理内で状態を更新する場合は特に注意が必要です。

例:依存関係の指定による挙動の違い

以下のコードでは、ディペンデンシー配列によって異なる動作が示されます:

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

function DependencyExample() {
    const [count, setCount] = useState(0);
    const [text, setText] = useState('');

    // 初回レンダリングとcount変更時に実行
    useEffect(() => {
        console.log(`Count updated: ${count}`);
    }, [count]);

    // 初回レンダリングとtext変更時に実行
    useEffect(() => {
        console.log(`Text updated: ${text}`);
    }, [text]);

    // 初回レンダリング時に一度だけ実行
    useEffect(() => {
        console.log('This runs only once');
    }, []);

    return (
        <div>
            <button onClick={() => setCount(count + 1)}>Increment Count</button>
            <input
                value={text}
                onChange={(e) => setText(e.target.value)}
                placeholder="Type something"
            />
        </div>
    );
}

ディペンデンシー配列が適切でない場合のリスク

  • 必要以上に頻繁に実行される
    配列を空にしない場合、不要な再実行が発生してパフォーマンスが低下します。
  • 更新されない値を参照する
    依存関係が不足していると、useEffect内で古い値を参照するバグが発生します。

結論

ディペンデンシー配列は、useEffectの実行タイミングを適切に制御するために欠かせません。配列に含める依存関係を明確に理解し、正確に指定することで、副作用処理を効率的に管理できます。この基礎を押さえることで、次にクリーンアップ処理などの高度な使い方に進む準備が整います。

useEffectでのクリーンアップ処理

useEffectを使用する際、メモリリークや不必要な副作用を防ぐためにクリーンアップ処理を適切に実装することが重要です。クリーンアップ処理は、コンポーネントがアンマウントされるときや、useEffectが再実行される直前に実行される特別な機能です。

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

クリーンアップ処理が必要になる主な理由は以下の通りです:

  1. リソースの解放
    タイマーやサブスクリプション(例:WebSocket接続)が適切に解除されないと、メモリリークや意図しない動作の原因になります。
  2. 不要なリスナーの削除
    イベントリスナーが残ったままだと、意図しない動作やパフォーマンスの低下が発生します。
  3. 最新の状態を保持
    前回のuseEffectの処理が残っていると、古い状態を参照してしまう可能性があります。

クリーンアップ処理の実装

useEffect内でクリーンアップ処理を実装するには、返り値としてクリーンアップ用の関数を指定します。この関数は、useEffectが再実行される直前やコンポーネントのアンマウント時に呼び出されます。

useEffect(() => {
    // 副作用処理
    const intervalId = setInterval(() => {
        console.log('Interval running...');
    }, 1000);

    // クリーンアップ処理
    return () => {
        clearInterval(intervalId);
        console.log('Interval cleared.');
    };
}, []); // 依存関係に基づいて再実行

具体例:タイマーのクリーンアップ

以下の例では、タイマーの設定と解除を適切に管理しています:

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

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

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

        return () => {
            clearInterval(timer); // タイマーを解除
        };
    }, []);

    return (
        <div>
            <p>Count: {count}</p>
        </div>
    );
}

このコードでは、コンポーネントがアンマウントされたときにタイマーが解除され、不要な動作を防ぎます。

具体例:イベントリスナーのクリーンアップ

イベントリスナーも適切に解除しないと、メモリリークや予期しない動作を引き起こします。

import React, { useEffect } from 'react';

function EventListenerComponent() {
    useEffect(() => {
        const handleResize = () => {
            console.log(`Window resized to: ${window.innerWidth} x ${window.innerHeight}`);
        };

        window.addEventListener('resize', handleResize);

        return () => {
            window.removeEventListener('resize', handleResize); // リスナーを解除
        };
    }, []);

    return <p>Resize the window and check the console.</p>;
}

このコードでは、リスナーを追加すると同時に、必要に応じて削除も行っています。

クリーンアップ処理のベストプラクティス

  • 必ずクリーンアップを行う
    副作用処理で外部リソースを利用する場合は、忘れずにクリーンアップを実装する。
  • 依存関係に注意する
    クリーンアップ処理が再実行時に呼び出されるため、依存関係が適切に設定されていることを確認する。
  • メモリリークを防止する
    必要なくなったリソースは必ず解放し、アプリのパフォーマンスを保つ。

結論

クリーンアップ処理を適切に実装することで、Reactコンポーネントの副作用を正しく管理し、予期せぬバグやリソース浪費を防ぐことができます。この基本を押さえることで、より信頼性の高いアプリケーションを構築できるようになります。次に、APIコールなど、より実践的な副作用処理の例を見ていきましょう。

APIコールを伴う副作用処理の例

ReactのuseEffectは、外部APIと通信するような非同期操作を行う際にも非常に有用です。特に、データの取得やサーバーへの送信など、非同期処理における副作用を管理する場面で活躍します。

基本的なAPIコールの例

以下は、fetchを用いて外部APIからデータを取得する基本的な例です:

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

function ApiFetcher() {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        const fetchData = async () => {
            try {
                setLoading(true);
                const response = await fetch('https://api.example.com/data');
                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }
                const result = await response.json();
                setData(result);
            } catch (error) {
                setError(error.message);
            } finally {
                setLoading(false);
            }
        };

        fetchData();
    }, []); // 初回レンダリング時にのみ実行

    if (loading) return <p>Loading...</p>;
    if (error) return <p>Error: {error}</p>;

    return (
        <div>
            <h2>Data from API:</h2>
            <pre>{JSON.stringify(data, null, 2)}</pre>
        </div>
    );
}

export default ApiFetcher;

この例のポイント

  1. 非同期処理の実装
    useEffect内で直接async関数を使用することはできないため、非同期処理は別に定義した関数内で行います。
  2. 状態管理
    データ取得中のローディング状態やエラーメッセージを管理するために、loadingerrorの状態を追加しています。
  3. 依存関係の管理
    ディペンデンシー配列を空にすることで、初回レンダリング時にのみAPIコールが実行されます。

検索機能を伴うAPIコールの例

ユーザー入力に応じてAPIを呼び出すようなケースでは、ディペンデンシー配列を活用して動的にAPIコールを実行します。

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

function SearchableApiFetcher() {
    const [query, setQuery] = useState('react');
    const [data, setData] = useState(null);

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

        if (query.trim() !== '') {
            fetchData();
        }
    }, [query]); // queryが変更されるたびに実行

    return (
        <div>
            <input
                value={query}
                onChange={(e) => setQuery(e.target.value)}
                placeholder="Search..."
            />
            <h2>Search Results:</h2>
            <pre>{JSON.stringify(data, null, 2)}</pre>
        </div>
    );
}

この例のポイント

  1. 動的な依存関係
    ユーザーが入力したqueryが変更されるたびにuseEffectが再実行され、APIコールがトリガーされます。
  2. 条件付き実行
    ユーザー入力が空の場合にはAPIコールをスキップするようにしています。

APIコールのクリーンアップ処理

リアルタイムでデータを取得する場合や、ユーザー操作が速い場合、前回の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 result = await response.json();
            setData(result);
        } catch (error) {
            if (error.name !== 'AbortError') {
                console.error('Fetch error:', error);
            }
        }
    };

    fetchData();

    return () => {
        controller.abort(); // 前回のリクエストをキャンセル
    };
}, []);

クリーンアップのポイント

  • AbortControllerを利用することで、前回のリクエストをキャンセルし、不要なデータ取得を防ぎます。

結論

useEffectを利用したAPIコールは、状態管理や非同期処理の基本を理解する上で重要なスキルです。非同期処理の構造化やクリーンアップ処理を適切に行うことで、パフォーマンスや信頼性の高いReactアプリケーションを構築することが可能になります。次は、無限ループを回避するためのベストプラクティスについて解説します。

無限ループを回避するためのベストプラクティス

useEffectを使用する際、無限ループに陥るリスクがあります。これは副作用処理内で状態を更新し、それが再びuseEffectをトリガーして永遠に繰り返されるケースです。適切な方法で依存関係を管理し、無限ループを防ぐことが重要です。

無限ループが発生する原因

無限ループの主な原因は以下の通りです:

  1. 依存関係の誤り
    useEffect内で使用している状態や関数を依存配列に含め忘れる、または過剰に含めることで再レンダリングが繰り返される。
  2. 状態の不適切な更新
    useEffect内で直接的に状態を更新すると、依存関係により再実行がトリガーされる。
  3. 計算の不安定性
    関数やオブジェクトがレンダリングごとに再生成される場合、それを依存関係に指定すると無限ループが発生する。

無限ループを回避する方法

1. 必要な依存関係のみを指定する


依存配列には、useEffect内で使用しているすべての状態や変数を正確に指定します。しかし、計算結果など動的に変化する値を避けることで不要な再実行を防ぐことができます。

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

2. 状態更新時の条件を追加する


useEffect内で状態を更新する場合、条件分岐を追加して無限ループを回避します。

useEffect(() => {
    if (count < 10) {
        setCount(count + 1);
    }
}, [count]); // countが10未満の場合のみ更新

3. `useCallback`や`useMemo`を活用する


関数やオブジェクトを依存配列に指定する際、それがレンダリングごとに再生成されると無限ループの原因となります。useCallbackuseMemoを活用して、再生成を防ぎます。

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

function CallbackExample() {
    const [count, setCount] = useState(0);

    const increment = useCallback(() => {
        setCount((prev) => prev + 1);
    }, []); // 依存配列を空にして再生成を防ぐ

    useEffect(() => {
        increment();
    }, [increment]); // incrementに依存
}

4. クリーンアップ処理でリセットする


前回のuseEffectの副作用を解除することで、無限ループを防ぎます。

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

    return () => {
        clearInterval(timer); // 前回のタイマーを解除
    };
}, []); // 依存関係なし

例:無限ループの修正

以下の例では、無限ループを修正した実装を示します:

無限ループ発生の例:

useEffect(() => {
    setCount(count + 1); // 状態を直接更新
}, [count]); // 再実行が繰り返される

修正後の例:

useEffect(() => {
    if (count < 10) {
        setCount((prev) => prev + 1);
    }
}, [count]); // 条件を追加してループを回避

ベストプラクティスのまとめ

  1. 依存関係を明確に指定する
    必要な値のみを依存配列に含める。
  2. 状態更新に条件を追加する
    状態を更新する際には、条件を設けて再実行のトリガーを抑制する。
  3. 関数やオブジェクトの再生成を防ぐ
    useCallbackuseMemoで再生成を制御する。
  4. クリーンアップ処理を正しく実装する
    前回の副作用を適切に解除して重複を防ぐ。

結論

無限ループを回避するには、依存関係の管理と状態更新の条件設定が鍵です。これらのベストプラクティスを活用することで、パフォーマンスを向上させ、安定した副作用処理を実現できます。次は、リアルタイムデータ更新の実践的な例について解説します。

useEffectを使ったリアルタイムデータ更新の応用例

リアルタイムデータを表示する機能は、多くのWebアプリケーションで必要とされます。たとえば、チャットアプリ、株価情報、天気データなどでリアルタイムの更新を行うケースがあります。ReactのuseEffectを使えば、リアルタイムデータを効率的に取得し、コンポーネントの状態を更新することができます。

リアルタイムデータ更新の仕組み

リアルタイム更新の一般的な方法は以下の通りです:

  1. 定期的なポーリング
    一定間隔でサーバーから最新のデータを取得する方法。
  2. WebSocket
    サーバーからクライアントにデータをリアルタイムでプッシュする方法。
  3. サードパーティライブラリ
    FirebaseやPusherなどのリアルタイムデータ通信サービスを利用する方法。

定期的なポーリングの例

以下は、setIntervalを使用して一定間隔でデータを取得する例です:

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

function PollingExample() {
    const [data, setData] = useState(null);

    useEffect(() => {
        const fetchData = async () => {
            const response = await fetch('https://api.example.com/data');
            const result = await response.json();
            setData(result);
        };

        fetchData(); // 初回実行
        const interval = setInterval(fetchData, 5000); // 5秒ごとにデータ取得

        return () => clearInterval(interval); // クリーンアップでタイマー解除
    }, []);

    return (
        <div>
            <h2>Real-Time Data:</h2>
            <pre>{JSON.stringify(data, null, 2)}</pre>
        </div>
    );
}

export default PollingExample;

この例のポイント

  • 初回レンダリング時に即座にデータを取得。
  • その後、5秒ごとにデータを取得。
  • クリーンアップ処理でsetIntervalを解除し、不要なリソース使用を防ぐ。

WebSocketを使用したリアルタイムデータ更新の例

WebSocketを利用してサーバーからのリアルタイムデータを受信する方法もよく使われます。

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

function WebSocketExample() {
    const [messages, setMessages] = useState([]);

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

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

        return () => socket.close(); // クリーンアップで接続を閉じる
    }, []);

    return (
        <div>
            <h2>Messages:</h2>
            <ul>
                {messages.map((msg, index) => (
                    <li key={index}>{msg.text}</li>
                ))}
            </ul>
        </div>
    );
}

export default WebSocketExample;

この例のポイント

  • WebSocket接続を確立し、サーバーからのメッセージを受信。
  • クリーンアップ処理でsocket.close()を呼び出し、接続を正しく解除。

リアルタイムデータ更新のベストプラクティス

  1. 適切なクリーンアップ処理
    ポーリングやWebSocket接続は、コンポーネントのアンマウント時に確実に停止または解除する。
  2. 適切な間隔の設定
    ポーリング間隔は、アプリの要件やサーバーの負荷を考慮して設定する。
  3. エラーハンドリングの実装
    ネットワークエラーが発生した場合に再接続するロジックを追加する。
  4. 依存関係の管理
    データ取得や接続処理に必要な状態やプロパティを依存配列に正しく指定する。

複合的なリアルタイム更新の例

ポーリングとWebSocketを組み合わせたリアルタイム更新の例を以下に示します:

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

function CombinedExample() {
    const [data, setData] = useState([]);
    const [socketMessages, setSocketMessages] = useState([]);

    useEffect(() => {
        // ポーリング処理
        const fetchData = async () => {
            const response = await fetch('https://api.example.com/data');
            const result = await response.json();
            setData(result);
        };

        fetchData();
        const interval = setInterval(fetchData, 5000);

        // WebSocket処理
        const socket = new WebSocket('wss://example.com/socket');
        socket.onmessage = (event) => {
            const message = JSON.parse(event.data);
            setSocketMessages((prev) => [...prev, message]);
        };

        return () => {
            clearInterval(interval); // タイマー解除
            socket.close(); // ソケット接続解除
        };
    }, []);

    return (
        <div>
            <h2>Polling Data:</h2>
            <pre>{JSON.stringify(data, null, 2)}</pre>
            <h2>WebSocket Messages:</h2>
            <ul>
                {socketMessages.map((msg, index) => (
                    <li key={index}>{msg.text}</li>
                ))}
            </ul>
        </div>
    );
}

結論

リアルタイムデータ更新をuseEffectで管理することで、ユーザーに最新情報を提供するアプリケーションを構築できます。ポーリングやWebSocketの特性を理解し、適切なクリーンアップ処理やエラーハンドリングを実装することで、効率的で信頼性の高いリアルタイム更新が可能です。次は、これを実際に学習するための演習問題を見ていきます。

useEffectを使った演習問題

ここでは、useEffectの基本から応用までを理解するための演習問題を提示します。これらの問題を通じて、実際にコードを書きながら学習を深めていきましょう。

演習1:初回レンダリング時のAPIコール

目標
初回レンダリング時に外部APIからデータを取得して画面に表示するReactコンポーネントを作成してください。

要件

  1. APIエンドポイント:https://jsonplaceholder.typicode.com/posts
  2. データを取得し、画面にリスト形式で表示する。
  3. データ取得中は「Loading…」と表示する。

ヒント

  • useEffectを使用して、初回レンダリング時にデータを取得します。
  • ローディング状態を管理するためにuseStateを使います。
// スタートコード
function FetchPosts() {
    // 必要な状態を定義
    const [posts, setPosts] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        // データ取得処理を実装
    }, []); // 依存配列を適切に設定

    return (
        <div>
            <h2>Posts:</h2>
            {/* ローディング時とデータ表示の切り替え */}
        </div>
    );
}

演習2:検索フィールドとAPIコール

目標
検索フィールドで入力されたキーワードに基づいて、APIを呼び出して結果を表示します。

要件

  1. 検索キーワードを入力できるフィールドを作成。
  2. キーワードが変更されるたびにuseEffectでAPIを呼び出す。
  3. 検索結果を画面にリスト形式で表示する。
  4. 空のキーワードではAPIを呼び出さない。

ヒント

  • 入力値を管理するためにuseStateを使います。
  • APIエンドポイント例:https://api.example.com/search?q=キーワード
// スタートコード
function SearchComponent() {
    const [query, setQuery] = useState('');
    const [results, setResults] = useState([]);

    useEffect(() => {
        // クエリが空の場合は処理をスキップ
    }, [query]);

    return (
        <div>
            <input
                type="text"
                value={query}
                onChange={(e) => setQuery(e.target.value)}
                placeholder="Enter search term"
            />
            <h2>Results:</h2>
            <ul>
                {/* 結果をリストで表示 */}
            </ul>
        </div>
    );
}

演習3:リアルタイムタイマー

目標
現在時刻を1秒ごとに更新して表示するタイマーを作成してください。

要件

  1. 現在時刻を表示する。
  2. 1秒ごとにuseEffectで時刻を更新する。
  3. クリーンアップ処理を実装して、タイマーを正しく解除する。

ヒント

  • 現在時刻はnew Date()で取得できます。
  • タイマーにはsetIntervalを使用します。
// スタートコード
function RealTimeClock() {
    const [time, setTime] = useState(new Date());

    useEffect(() => {
        // タイマーの設定とクリーンアップを実装
    }, []); // 初回レンダリング時のみ実行

    return (
        <div>
            <h2>Current Time:</h2>
            <p>{time.toLocaleTimeString()}</p>
        </div>
    );
}

演習4:WebSocketでリアルタイムデータ取得

目標
WebSocketを使用してサーバーからのメッセージを受信し、それをリアルタイムで表示します。

要件

  1. サーバーURL:wss://example.com/socket
  2. メッセージをリスト形式で表示する。
  3. クリーンアップ処理でWebSocket接続を閉じる。

ヒント

  • WebSocketのイベントハンドラーonmessageを使用します。
  • クリーンアップ処理でsocket.close()を呼び出します。
// スタートコード
function WebSocketMessages() {
    const [messages, setMessages] = useState([]);

    useEffect(() => {
        // WebSocket接続を実装
    }, []); // 初回レンダリング時のみ実行

    return (
        <div>
            <h2>Messages:</h2>
            <ul>
                {/* メッセージをリスト形式で表示 */}
            </ul>
        </div>
    );
}

演習問題の解答方法

これらの演習を実際にコーディングし、動作を確認してください。リアルタイムでReactコンポーネントがどのように動作するのかを観察することで、useEffectの理解がさらに深まります。

結論

演習を通じて、useEffectを用いたさまざまなパターンの副作用処理に慣れてください。基本的なデータ取得からリアルタイム更新まで、実践的なシナリオを学ぶことで、Reactの開発スキルを向上させましょう。次は、これまでの知識を総括するまとめを行います。

まとめ

本記事では、ReactのuseEffectを使った状態変更時の副作用処理について基本から応用までを解説しました。useEffectの基本的な仕組み、ディペンデンシー配列の重要性、クリーンアップ処理の方法、APIコールやリアルタイムデータ更新の実例、そして無限ループを回避するためのベストプラクティスを学びました。また、演習問題を通じて、実践的なシナリオに対応するスキルを磨くこともできました。

useEffectは、適切に使用することでReactアプリケーションのパフォーマンスや信頼性を大きく向上させます。これらの知識を活用し、安定した副作用処理を備えた高品質なReactアプリケーションを開発していきましょう。

コメント

コメントする

目次