MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
Reactの基礎 - useEffectのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
Reactの基礎 - useEffect
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == <code>useEffect</code> は、Reactの関数コンポーネントで副作用 (side effects) を扱うための標準的なHookである。<br> <u>副作用とは、コンポーネントのレンダリング自体とは直接関係のない処理を指し、データフェッチ、DOM操作、タイマの設定、イベントリスナーの登録等が該当する。</u><br> <br> React 16.8以前のクラスコンポーネントでは、ライフサイクルメソッド (<code>componentDidMount</code>、<code>componentDidUpdate</code>、<code>componentWillUnmount</code>) を使用して副作用を管理していた。<br> <code>useEffect</code> はこれら3つのライフサイクルメソッドの役割を1つのAPIに統合したものである。<br> <br> 基本構文は <code>useEffect(setup, dependencies?)</code> であり、第1引数にEffectのロジックを記述した関数、第2引数に依存配列をオプションで指定する。<br> 依存配列の指定方法によって、Effectが実行されるタイミングを制御する。<br> <br> また、setup関数からクリーンアップ関数を返すことにより、コンポーネントのアンマウント時やEffectの再実行前に後処理を実行できる。<br> クリーンアップ関数を正しく実装することで、メモリリークや意図しない副作用の蓄積を防止できる。<br> <br> なお、<code>useEffect</code> はReact 18以降の開発モード (StrictMode) では意図的に2回実行される。<br> これはクリーンアップ関数の正しい実装を検証するための仕組みであり、本番環境では1回のみ実行される。<br> <br><br> == 依存配列の制御 == <code>useEffect</code> の第2引数に渡す依存配列によって、Effectが実行されるタイミングが変化する。<br> <br> ==== 依存配列なし ==== 依存配列を省略すると、コンポーネントのレンダリングごとにEffectが実行される。<br> <br> <syntaxhighlight lang="typescript"> import { useEffect } from 'react'; function ExampleComponent() { useEffect(() => { // 全レンダリング後に実行される console.log('レンダリングが実行された'); }); return <div>コンポーネント</div>; } </syntaxhighlight> <br> 依存配列なしのEffectは毎回実行されるため、データフェッチや重い処理に使用するとパフォーマンス上の問題が生じる場合がある。<br> 通常は依存配列を明示的に指定することを推奨する。<br> <br> ==== 空配列 [] ==== 空配列を指定すると、コンポーネントのマウント時に1回だけEffectが実行される。<br> クラスコンポーネントの <code>componentDidMount</code> に相当する動作である。<br> <br> <syntaxhighlight lang="typescript"> import { useEffect, useState } from 'react'; function DataLoader() { const [data, setData] = useState<string[]>([]); useEffect(() => { // マウント時に1回だけ実行される console.log('コンポーネントがマウントされた'); fetchInitialData().then(setData); }, []); return <ul>{data.map((item, i) => <li key={i}>{item}</li>)}</ul>; } </syntaxhighlight> <br> ==== 特定の値を指定 ==== 依存配列に特定の値を指定すると、それらの値が変更された時にEffectが実行される。<br> クラスコンポーネントの <code>componentDidUpdate</code> に相当する動作である。<br> <br> <syntaxhighlight lang="typescript"> import { useEffect, useState } from 'react'; interface UserProfileProps { userId: number; } function UserProfile({ userId }: UserProfileProps) { const [user, setUser] = useState<{ name: string } | null>(null); useEffect(() => { // userId が変更されるたびに実行される fetchUser(userId).then(setUser); }, [userId]); return <div>{user?.name ?? '読み込み中...'}</div>; } </syntaxhighlight> <br> 依存配列に指定する値は、Effectの内部で参照する全てのリアクティブ値 (props、state、コンポーネント内で宣言された変数・関数) を含める必要がある。<br> <br><br> == クリーンアップ関数 == <code>useEffect</code> の setup関数からクリーンアップ関数を返すことで、以下に示すタイミングで後処理を実行できる。<br> <br> * コンポーネントがアンマウントされる時 * Effectが再実行される前 (前回のEffectのクリーンアップが先に行われる) <br> ==== タイマのクリーンアップ ==== <code>setInterval</code> や <code>setTimeout</code> を使用する場合は、クリーンアップ関数で必ず解除する必要がある。<br> 解除しない場合、コンポーネントのアンマウント後もタイマが動作し続け、メモリリークや意図しない状態更新の原因となる。<br> <br> <syntaxhighlight lang="typescript"> import { useEffect, useState } from 'react'; function Timer() { const [count, setCount] = useState(0); useEffect(() => { const intervalId = setInterval(() => { setCount(prev => prev + 1); }, 1000); // クリーンアップ関数でタイマを解除する return () => { clearInterval(intervalId); }; }, []); return <div>経過秒数: {count}秒</div>; } </syntaxhighlight> <br> ==== イベントリスナーの解除 ==== <code>addEventListener</code> で登録したイベントリスナーは、クリーンアップ関数で <code>removeEventListener</code> を使用して解除する。<br> <br> <syntaxhighlight lang="typescript"> import { useEffect, useState } from 'react'; function WindowSizeTracker() { const [windowWidth, setWindowWidth] = useState(window.innerWidth); useEffect(() => { const handleResize = () => { setWindowWidth(window.innerWidth); }; window.addEventListener('resize', handleResize); // クリーンアップ関数でイベントリスナーを解除する return () => { window.removeEventListener('resize', handleResize); }; }, []); return <div>ウィンドウ幅: {windowWidth}px</div>; } </syntaxhighlight> <br> ==== WebSocket・サブスクリプションの切断 ==== WebSocketやリアルタイムサブスクリプションを使用する場合も、クリーンアップ関数で切断処理を定義する。<br> <br> <syntaxhighlight lang="typescript"> import { useEffect, useState } from 'react'; interface Message { id: number; text: string; } function ChatRoom() { const [messages, setMessages] = useState<Message[]>([]); useEffect(() => { const ws = new WebSocket('wss://example.com/chat'); ws.onmessage = (event: MessageEvent) => { const msg = JSON.parse(event.data) as Message; setMessages(prev => [...prev, msg]); }; // クリーンアップ関数でWebSocketを切断する return () => { ws.close(); }; }, []); return ( <ul> {messages.map(msg => <li key={msg.id}>{msg.text}</li>)} </ul> ); } </syntaxhighlight> <br><br> == データフェッチング == <code>useEffect</code> を使用したデータフェッチングは一般的なパターンである。<br> <br> <u>ただし、<code>useEffect</code> 自体は非同期関数を直接返すことができないため、内部で非同期関数を定義して呼び出す形式で記述する。</u><br> <br> ==== 基本的なfetchパターン ==== データフェッチングでは、コンポーネントのアンマウント後にstateを更新しないよう <code>ignore</code> フラグを使用して競合状態 (race condition) を防止することが重要である。<br> <br> <syntaxhighlight lang="typescript"> import { useEffect, useState } from 'react'; interface Post { id: number; title: string; body: string; } interface PostDetailProps { postId: number; } function PostDetail({ postId }: PostDetailProps) { const [post, setPost] = useState<Post | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { // ignoreフラグで競合状態を防止する let ignore = false; setLoading(true); setError(null); async function fetchPost() { try { const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`); const data = await response.json() as Post; if (!ignore) { setPost(data); } } catch (err) { if (!ignore) { setError('データの取得に失敗した'); } } finally { if (!ignore) { setLoading(false); } } } fetchPost(); // クリーンアップ関数でignoreフラグをtrueにする return () => { ignore = true; }; }, [postId]); if (loading) return <div>読み込み中...</div>; if (error) return <div>エラー: {error}</div>; if (!post) return null; return ( <article> <h1>{post.title}</h1> <p>{post.body}</p> </article> ); } </syntaxhighlight> <br> ==== AbortControllerによるキャンセル ==== <code>AbortController</code> を使用すると、コンポーネントのアンマウント時に進行中のfetchリクエストをキャンセルできる。<br> <u><code>ignore</code> フラグと組み合わせることにより、より安全なデータフェッチングを実装できる。</u><br> <br> <syntaxhighlight lang="typescript"> import { useEffect, useState } from 'react'; interface SearchResult { id: number; name: string; } interface SearchProps { query: string; } function SearchResults({ query }: SearchProps) { const [results, setResults] = useState<SearchResult[]>([]); useEffect(() => { if (!query) { setResults([]); return; } const controller = new AbortController(); async function fetchResults() { try { const response = await fetch(`/api/search?q=${query}`, { signal: controller.signal }); const data = await response.json() as SearchResult[]; setResults(data); } catch (err) { if ((err as Error).name !== 'AbortError') { console.error('検索エラー:', err); } } } fetchResults(); // クリーンアップ関数でリクエストをキャンセルする return () => { controller.abort(); }; }, [query]); return ( <ul> {results.map(result => <li key={result.id}>{result.name}</li>)} </ul> ); } </syntaxhighlight> <br><br> == useEffect と useLayoutEffectの違い == Reactには <code>useEffect</code> と似たHookとして <code>useLayoutEffect</code> が存在する。<br> 両者の主な違いは実行タイミングである。<br> <br> <center> {| class="wikitable" |+ useEffectとuseLayoutEffectの比較 ! 項目 !! useEffect !! useLayoutEffect |- | 実行タイミング || ブラウザの画面描画後に非同期で実行 || Webブラウザの画面描画前に同期で実行 |- | パフォーマンスへの影響 || 描画をブロックしないため影響が少ない。 || 描画をブロックするため影響が大きい。 |- | 主な用途 || データフェッチ、イベント登録、外部連携 || DOM計測、スクロール位置調整、ちらつき防止 |- | SSRでの動作 || 通常通り動作する || サーバサイドでは実行されない。(警告が発生する場合がある) |} </center> <br> <u><code>useLayoutEffect</code> を使用するのは、画面のちらつきを防止する必要がある場合に限定することを推奨する。</u><br> 例えば、DOMの計測値に基づいてスタイルを同期的に適用する場合等が該当する。<br> <br> 通常の副作用処理には <code>useEffect</code> を使用し、DOM計測やちらつき防止が必要な場合のみ <code>useLayoutEffect</code> を使用する。<br> <br> <syntaxhighlight lang="typescript"> import { useLayoutEffect, useRef, useState } from 'react'; function TooltipWithPosition() { const ref = useRef<HTMLDivElement>(null); const [tooltipHeight, setTooltipHeight] = useState(0); // DOM計測を描画前に行うためuseLayoutEffectを使用する useLayoutEffect(() => { if (ref.current) { setTooltipHeight(ref.current.offsetHeight); } }, []); return ( <div> <div ref={ref}>ツールチップコンテンツ</div> <p>ツールチップの高さ: {tooltipHeight}px</p> </div> ); } </syntaxhighlight> <br><br> == Tauri v2との統合 == Tauri v2アプリケーションでは、<code>useEffect</code> 内でTauriの <code>invoke</code> 関数を呼び出して、Rustバックエンドのコマンドと連携することができる。<br> <br> ==== invokeコマンドの呼び出し ==== <code>@tauri-apps/api/core</code> から <code>invoke</code> をインポートして、<code>useEffect</code> 内で非同期処理として呼び出す。<br> <br> <syntaxhighlight lang="typescript"> import { useEffect, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; interface SystemInfo { os: string; arch: string; version: string; } function SystemInfoDisplay() { const [sysInfo, setSysInfo] = useState<SystemInfo | null>(null); const [error, setError] = useState<string | null>(null); useEffect(() => { let ignore = false; async function loadSystemInfo() { try { // Rustバックエンドのget_system_infoコマンドを呼び出す const info = await invoke<SystemInfo>('get_system_info'); if (!ignore) { setSysInfo(info); } } catch (err) { if (!ignore) { setError(`システム情報の取得に失敗した: ${err}`); } } } loadSystemInfo(); return () => { ignore = true; }; }, []); if (error) return <div>エラー: {error}</div>; if (!sysInfo) return <div>読み込み中...</div>; return ( <div> <p>OS: {sysInfo.os}</p> <p>アーキテクチャ: {sysInfo.arch}</p> <p>バージョン: {sysInfo.version}</p> </div> ); } </syntaxhighlight> <br> propsの変化に応じてバックエンドコマンドを再呼び出しする場合は、依存配列に該当するpropsを指定する。<br> <br> <syntaxhighlight lang="typescript"> import { useEffect, useState } from 'react'; import { invoke } from '@tauri-apps/api/core'; interface FileContentProps { filePath: string; } function FileContentViewer({ filePath }: FileContentProps) { const [content, setContent] = useState<string>(''); useEffect(() => { let ignore = false; async function readFile() { try { const text = await invoke<string>('read_file', { path: filePath }); if (!ignore) { setContent(text); } } catch (err) { console.error('ファイル読み込みエラー:', err); } } readFile(); return () => { ignore = true; }; }, [filePath]); return <pre>{content}</pre>; } </syntaxhighlight> <br><br> == StrictModeでの2重実行 == <u>React 18以降の開発モードでは、<code>useEffect</code> が意図的に2回実行される。</u><br> この動作はReactのStrictModeによるものである。<br> <br> StrictModeは開発時にクリーンアップ関数が正しく実装されているかを検証するため、Effectのマウント・クリーンアップ・再マウントのサイクルをシミュレートする。<br> <br> 具体的な実行順序は以下の通りである。<br> <br> # コンポーネントがマウントされ、Effectが実行される。 # クリーンアップ関数が実行される。 # 同じコンポーネントが再マウントされ、Effectが再実行される。 <br> <u>この2重実行は本番環境では発生せず、開発モード (StrictMode) 限定の動作である。</u><br> <br> <syntaxhighlight lang="typescript"> import { useEffect } from 'react'; function StrictModeExample() { useEffect(() => { // 開発モードでは2回呼ばれる console.log('Effect実行'); return () => { // 開発モードでは2回呼ばれる console.log('クリーンアップ実行'); }; }, []); return <div>StrictModeテスト</div>; } </syntaxhighlight> <br> 2重実行が問題となる場合は、クリーンアップ関数を正しく実装することで対処する。<br> クリーンアップ関数が適切に実装されていれば、StrictModeでの2重実行によって不具合は生じない。<br> <br><br> == よくある間違い == <code>useEffect</code> の使用において頻繁に発生する間違いを以下に示す。<br> <br> ==== 依存配列の欠落 ==== Effectの内部で参照するpropsやstateを依存配列に含めない場合、古い値を参照し続けるバグが発生する可能性がある。<br> <br> <syntaxhighlight lang="typescript"> // 誤り : userIdを参照しているが依存配列に含まれていない useEffect(() => { fetchUser(userId); }, []); // userIdが変わっても再実行されない // 正しい : 参照している値を依存配列に含める useEffect(() => { fetchUser(userId); }, [userId]); </syntaxhighlight> <br> ESLintの <code>exhaustive-deps</code> ルールを使用すると、依存配列の欠落を自動検出できる。<br> <br> ==== 無限ループ ==== Effect内でstateを更新し、そのstateが依存配列に含まれている場合、無限ループが発生する。<br> <br> <syntaxhighlight lang="typescript"> // 誤り : dataを更新するEffectがdataに依存しているため無限ループが発生する const [data, setData] = useState([]); useEffect(() => { setData([...data, 'newItem']); // dataを更新する }, [data]); // dataが変わるたびに再実行される // 正しい : アップデーター関数を使用してdataへの依存をなくす useEffect(() => { setData(prev => [...prev, 'newItem']); }, []); // 依存配列からdataを除外できる </syntaxhighlight> <br> ==== 非同期関数の直接使用 ==== <code>useEffect</code> の第1引数に非同期関数を直接渡すことはできない。<br> 非同期関数はPromiseを返すが、<code>useEffect</code> はクリーンアップ関数 (または何も返さない) のみを期待しているためである。<br> <br> <syntaxhighlight lang="typescript"> // 誤り : useEffectに非同期関数を直接渡している useEffect(async () => { const data = await fetchData(); // async関数はPromiseを返すためエラーになる setData(data); }, []); // 正しい : useEffect内で非同期関数を定義して呼び出す useEffect(() => { async function loadData() { const data = await fetchData(); setData(data); } loadData(); }, []); </syntaxhighlight> <br> ==== 関連性のない処理の混在 ==== 複数の独立した副作用を1つの <code>useEffect</code> にまとめると、保守性が低下する。<br> 関連性のない処理は、個別の <code>useEffect</code> に分離することを推奨する。<br> <br> <syntaxhighlight lang="typescript"> // 誤り : 無関係な2つの副作用が混在している useEffect(() => { fetchUserData(userId); document.title = `ページ: ${pageTitle}`; }, [userId, pageTitle]); // 正しい : 独立した副作用はそれぞれのuseEffectに分離する useEffect(() => { fetchUserData(userId); }, [userId]); useEffect(() => { document.title = `ページ: ${pageTitle}`; }, [pageTitle]); </syntaxhighlight> <br><br> == 関連情報 == * [[Reactの基礎 - Hooksの基礎]] * [[Reactの基礎 - useState]] * [[Reactの基礎 - useRef]] * [[Reactの基礎 - カスタムHook]] <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板,React,JavaScript,TypeScript,TSX,useEffect,Hooks,副作用,side effects,データフェッチ,クリーンアップ,AbortController,useLayoutEffect,Tauri,StrictMode |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Rust]][[カテゴリ:Web]]
Reactの基礎 - useEffect
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse