MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
Reactの基礎 - useRefのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
Reactの基礎 - useRef
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == <code>useRef</code> は、レンダリングに必要のない値への参照を保持するためのHookである。<br> 基本構文は <code>const ref = useRef(initialValue)</code> であり、<code>{ current: T }</code> 構造のRefObjectを返す。<br> <br> <u><code>useRef</code> の最大の特徴は、<code>ref.current</code> の値を変更しても再レンダリングが発生しない点である。</u><br> これは値の変更があるたびにコンポーネントを再描画する <code>useState</code> とは根本的に異なる動作である。<br> <br> <code>useRef</code> の主な用途は以下の2つに大別される。<br> <br> * DOM要素への参照 *: <code>ref</code> 属性にRefObjectを渡すことで、DOMノードへの直接アクセスが可能になる。 *: フォーカス制御、スクロール操作、メディア再生等に利用する。 * ミュータブルな値の保持 *: レンダリングをまたいで値を保持し続けるが、画面描画には影響しない。 *: タイマIDや前回の値、WebSocketインスタンスの保持に利用する。 <br> TypeScriptでは、<code>useRef<HTMLInputElement>(null)</code> のように型引数を指定することで、型安全なDOM操作が可能になる。<br> <br><br> == DOM要素への参照 == ReactではDOM要素を直接操作するために <code>useRef</code> を使用する。<br> <code>ref</code> 属性にRefObjectを渡すことにより、コンポーネントのマウント後に <code>ref.current</code> からDOMノードへアクセスできる。<br> <br> ==== TypeScriptでのDOM型指定 ==== TypeScriptで <code>useRef</code> を使用する場合、型引数にDOM要素の型を指定する必要がある。<br> 初期値には <u>null</u> を指定して、型引数でアクセスするDOM要素の型を明示する。<br> <br> 下表に、よく使用されるDOM要素の型を示す。<br> <br> <center> {| class="wikitable" |+ よく使用されるDOM要素の型 ! 型名 !! 対象要素 !! 使用例 |- | HTMLInputElement || <code><input></code> || テキスト入力、フォーカス制御 |- | HTMLTextAreaElement || <code><textarea></code> || 複数行テキスト入力 |- | HTMLButtonElement || <code><button></code> || ボタン操作 |- | HTMLDivElement || <code><div></code> || スクロール制御、レイアウト操作 |- | HTMLVideoElement || <code><video></code> || 動画再生・一時停止 |- | HTMLCanvasElement || <code><canvas></code> || Canvas描画 |} </center> <br> TypeScriptでの基本的な型指定を以下に示す。<br> <br> <syntaxhighlight lang="typescript"> import { useRef } from 'react'; function MyComponent() { // HTMLInputElement型を指定し、初期値はnull const inputRef = useRef<HTMLInputElement>(null); // HTMLDivElement型を指定 const divRef = useRef<HTMLDivElement>(null); // HTMLVideoElement型を指定 const videoRef = useRef<HTMLVideoElement>(null); return ( <div> <input ref={inputRef} /> <div ref={divRef}>コンテンツ</div> <video ref={videoRef} src="video.mp4" /> </div> ); } </syntaxhighlight> <br> ==== DOM操作の例 ==== DOM要素への参照を活用した操作例を以下に示す。<br> <code>ref.current</code> が <u>null</u> になる場合があるため、オプショナルチェーン (<code>?.</code>) を使用して安全にアクセスする。<br> <br> * フォーカス制御の例 *: 以下の例では、ボタンを押下すると入力欄にフォーカスを当てている。 *: <syntaxhighlight lang="typescript"> import { useRef } from 'react'; export default function FocusExample() { const inputRef = useRef<HTMLInputElement>(null); const handleFocus = () => { // オプショナルチェーンで安全にアクセス inputRef.current?.focus(); }; return ( <div> <input ref={inputRef} placeholder="テキストを入力" /> <button onClick={handleFocus}>フォーカス</button> </div> ); } </syntaxhighlight> *: <br> * スクロール操作の例 *: 以下の例では、ボタンを押下すると特定の要素までスクロールしている。 *: <syntaxhighlight lang="typescript"> import { useRef } from 'react'; export default function ScrollExample() { const targetRef = useRef<HTMLDivElement>(null); const handleScroll = () => { targetRef.current?.scrollIntoView({ behavior: 'smooth' }); }; return ( <div> <button onClick={handleScroll}>ターゲットへスクロール</button> <div style={{ height: '200px' }} /> <div ref={targetRef}>ここにスクロールする</div> </div> ); } </syntaxhighlight> *: <br> * メディア再生の例 *: 以下の例では、動画の再生・一時停止を制御している。 *: <syntaxhighlight lang="typescript"> import { useRef } from 'react'; export default function VideoPlayer() { const videoRef = useRef<HTMLVideoElement>(null); const handlePlay = () => videoRef.current?.play(); const handlePause = () => videoRef.current?.pause(); return ( <div> <video ref={videoRef} src="video.mp4" /> <button onClick={handlePlay}>再生</button> <button onClick={handlePause}>一時停止</button> </div> ); } </syntaxhighlight> <br><br> == useRefとuseStateの違い == <code>useRef</code> と <code>useState</code> はどちらも値を保持するが、再レンダリングへの影響とミュータブル性に大きな違いがある。<br> <br> 下表に、両者の比較を示す。<br> <br> <center> {| class="wikitable" |+ useRefとuseStateの比較 ! 項目 !! useRef !! useState |- | 再レンダリング || 値を変更しても発生しない。 || 値を変更すると発生する。 |- | ミュータブル性 || <code>ref.current</code> を直接変更可能 || <code>setState</code> 関数を経由して変更する。 |- | 読み取り方法 || <code>ref.current</code> で読み取る。 || 変数名で直接読み取る。 |- | 主な用途 || DOM操作、タイマID、ミュータブルな値 || UIに反映させる状態管理 |- | TypeScript型 || <code>RefObject<T></code> または <code>MutableRefObject<T></code> || <code>[T, Dispatch<SetStateAction<T>>]</code> |} </center> <br> 両者の動作の違いを確認する。<br> <br> <syntaxhighlight lang="typescript"> import { useRef, useState } from 'react'; export default function Counter() { // useStateの場合 : 値を変更すると再レンダリングが発生する const [stateCount, setStateCount] = useState(0); // useRefの場合 : 値を変更しても再レンダリングは発生しない const refCount = useRef(0); const handleStateIncrement = () => { setStateCount(stateCount + 1); // 再レンダリングが発生する }; const handleRefIncrement = () => { refCount.current += 1; // 再レンダリングは発生しない console.log('refCount:', refCount.current); // コンソールには出力されるが画面は変わらない }; return ( <div> <p>useState: {stateCount} (画面に反映される)</p> <p>useRef: 変化しているが画面には反映されない</p> <button onClick={handleStateIncrement}>useState + 1</button> <button onClick={handleRefIncrement}>useRef + 1</button> </div> ); } </syntaxhighlight> <br><br> == ミュータブルな値の保持 == <code>useRef</code> はDOMへの参照だけでなく、レンダリングをまたいで値を保持し続けるミュータブルなコンテナとしても使用できる。<br> <br> ==== 前回の値の保持 ==== 前回のレンダリング時の値を保持したい場合、<code>useRef</code> を使用したカスタムHookパターンが有効である。<br> <br> <syntaxhighlight lang="typescript"> import { useEffect, useRef } from 'react'; // usePreviousカスタムHook : 前回の値を返す function usePrevious<T>(value: T): T | undefined { const ref = useRef<T | undefined>(undefined); useEffect(() => { // エフェクト内で更新することで、レンダリング後に前回の値が保存される ref.current = value; }); return ref.current; } export default function PreviousValueExample() { const [count, setCount] = useState(0); const prevCount = usePrevious(count); return ( <div> <p>現在の値: {count}</p> <p>前回の値: {prevCount}</p> <button onClick={() => setCount(count + 1)}>増加</button> </div> ); } </syntaxhighlight> <br> ==== タイマIDの保持 ==== <code>setInterval</code> や <code>setTimeout</code> のIDを保持してタイマを管理する場合、<code>useRef</code> が適切である。<br> タイマIDはUIの表示には不要であるため、<code>useState</code> ではなく <code>useRef</code> を使用する。<br> <br> <syntaxhighlight lang="typescript"> import { useRef, useState } from 'react'; export default function TimerExample() { const [count, setCount] = useState(0); const intervalIdRef = useRef<number | null>(null); const handleStart = () => { if (intervalIdRef.current !== null) return; // 既に起動中なら何もしない intervalIdRef.current = window.setInterval(() => { setCount(prev => prev + 1); }, 1000); }; const handleStop = () => { if (intervalIdRef.current === null) return; clearInterval(intervalIdRef.current); intervalIdRef.current = null; }; return ( <div> <p>カウント: {count}</p> <button onClick={handleStart}>開始</button> <button onClick={handleStop}>停止</button> </div> ); } </syntaxhighlight> <br> ==== WebSocketインスタンスの保持 ==== WebSocketのようなミュータブルなオブジェクトを保持する場合も、<code>useRef</code> が適切である。<br> 接続オブジェクト自体は画面に表示しないため、再レンダリングを発生させる必要がない。<br> <br> <syntaxhighlight lang="typescript"> import { useEffect, useRef, useState } from 'react'; export default function WebSocketExample() { const wsRef = useRef<WebSocket | null>(null); const [messages, setMessages] = useState<string[]>([]); useEffect(() => { // WebSocket接続をrefで保持する wsRef.current = new WebSocket('wss://example.com/ws'); wsRef.current.onmessage = (event) => { setMessages(prev => [...prev, event.data]); }; return () => { wsRef.current?.close(); }; }, []); const handleSend = (message: string) => { wsRef.current?.send(message); }; return ( <div> {messages.map((msg, i) => <p key={i}>{msg}</p>)} <button onClick={() => handleSend('Hello')}>送信</button> </div> ); } </syntaxhighlight> <br><br> == React 19でのrefの変更 == React 19では、<code>ref</code> の扱いに重要な変更が加えられた。<br> <code>forwardRef()</code> を使用せずに <code>ref</code> を通常のpropとして渡せるようになり、ソースコードが大幅に簡潔になった。<br> <br> ==== ref as prop ==== React 18以前では、子コンポーネントに <code>ref</code> を渡すためには <code>forwardRef()</code> でコンポーネントをラップする必要があった。<br> <u>React 19以降は、<code>ref</code> を通常のpropとして受け取れるため、<code>forwardRef()</code> が不要になった。</u><br> <br> * React 18以前の実装 *: <code>forwardRef()</code> を使用してrefを転送する方法である。 *: <syntaxhighlight lang="typescript"> import { forwardRef, useRef } from 'react'; interface MyInputProps { label: string; } // React 18以前 : forwardRefでラップが必要 const MyInput = forwardRef<HTMLInputElement, MyInputProps>( function MyInput({ label }, ref) { return ( <div> <label>{label}</label> <input ref={ref} /> </div> ); } ); export default function App() { const inputRef = useRef<HTMLInputElement>(null); return <MyInput label="名前" ref={inputRef} />; } </syntaxhighlight> *: <br> * React 19以降の実装 *: <code>ref</code> を通常のpropとして受け取る方法である。 *: <syntaxhighlight lang="typescript"> import { useRef } from 'react'; interface MyInputProps { label: string; ref?: React.Ref<HTMLInputElement>; } // React 19以降 : forwardRefは不要、refを通常のpropとして受け取る function MyInput({ label, ref }: MyInputProps) { return ( <div> <label>{label}</label> <input ref={ref} /> </div> ); } export default function App() { const inputRef = useRef<HTMLInputElement>(null); return <MyInput label="名前" ref={inputRef} />; } </syntaxhighlight> <br><br> == useImperativeHandle == <code>useImperativeHandle</code> は、親コンポーネントに公開するメソッドやプロパティを制御するHookである。<br> 子コンポーネントの実装の詳細を隠蔽しつつ、親が必要とするAPIのみを公開できる。<br> <br> <syntaxhighlight lang="typescript"> import { useRef, useImperativeHandle } from 'react'; interface InputHandle { focus: () => void; clear: () => void; getValue: () => string; } interface MyInputProps { ref?: React.Ref<InputHandle>; } function MyInput({ ref }: MyInputProps) { const inputRef = useRef<HTMLInputElement>(null); // 親に公開するAPIを定義する useImperativeHandle(ref, () => ({ focus() { inputRef.current?.focus(); }, clear() { if (inputRef.current) { inputRef.current.value = ''; } }, getValue() { return inputRef.current?.value ?? ''; }, }), []); // 第3引数は依存配列 return <input ref={inputRef} />; } export default function App() { const inputHandle = useRef<InputHandle>(null); return ( <div> <MyInput ref={inputHandle} /> <button onClick={() => inputHandle.current?.focus()}>フォーカス</button> <button onClick={() => inputHandle.current?.clear()}>クリア</button> <button onClick={() => console.log(inputHandle.current?.getValue())}>値を取得</button> </div> ); } </syntaxhighlight> <br><br> == コールバックref == <code>ref</code> 属性には、RefObjectだけでなくコールバック関数を渡すこともできる。<br> コールバックrefは、要素がマウントされたときにDOM要素が引数として渡され、アンマウントされた時に <u>null</u> が渡される。<br> <br> <syntaxhighlight lang="typescript"> import { useCallback, useRef } from 'react'; export default function CallbackRefExample() { // useCallbackでラップして不要な再呼び出しを防止する const inputCallbackRef = useCallback((node: HTMLInputElement | null) => { if (node !== null) { // マウント時 : DOM要素が渡される node.focus(); console.log('マウント:', node); } else { // アンマウント時 : nullが渡される console.log('アンマウント'); } }, []); // 依存配列が空のため、関数は一度だけ生成される return <input ref={inputCallbackRef} placeholder="マウント時に自動フォーカス" />; } </syntaxhighlight> <br> <u>コールバックrefは、要素のマウント・アンマウントのタイミングで処理を行う場合に特に有効である。</u><br> <u><code>useCallback</code> でラップしない場合、親コンポーネントが再レンダリングされるたびにコールバック関数が再生成され、不要な再呼び出しが発生するため注意が必要である。</u><br> <br><br> == よくある間違い == <code>useRef</code> の使用にあたって、よくある間違いと正しい対処法を以下に示す。<br> <br> ==== レンダリング中のref読み書き ==== <u>render関数本体でのレンダリング中に <code>ref.current</code> を読み書きしてはいけない。</u><br> 初期化処理等を除いて、<code>ref.current</code> へのアクセスはイベントハンドラや <code>useEffect</code> 内で行う。<br> <br> <syntaxhighlight lang="typescript"> import { useEffect, useRef } from 'react'; export default function WrongUsageExample() { const ref = useRef(0); // 正しい : useEffect内でrefを更新する useEffect(() => { ref.current += 1; console.log('レンダリング回数:', ref.current); }); // 誤り : レンダリング中 (render関数本体) でrefを書き換えている // ref.current += 1; // Reactの描画処理と競合するため禁止 return <div>コンポーネント</div>; } </syntaxhighlight> <br> ==== レンダリング用データへのref使用 ==== <u>画面に表示する値を <code>useRef</code> で管理してはいけない。</u><br> <code>ref.current</code> の変更は再レンダリングを発生させないため、画面に反映されない。<br> <br> <u>UIに表示する値は <code>useState</code> で管理する。</u><br> <br> <syntaxhighlight lang="typescript"> import { useRef, useState } from 'react'; export default function DataManagementExample() { // 正しい : 画面に表示する値はuseStateで管理する const [count, setCount] = useState(0); // 正しい : 画面に表示しない値はuseRefで管理する const renderCountRef = useRef(0); renderCountRef.current += 1; // 誤り : 画面に表示する値をrefで管理している // const countRef = useRef(0); // countRef.current += 1; // 画面が更新されない return ( <div> <p>カウント (画面に反映される): {count}</p> <p>レンダリング回数 (ログ用): {renderCountRef.current}</p> <button onClick={() => setCount(count + 1)}>増加</button> </div> ); } </syntaxhighlight> <br><br> == 関連情報 == * [[Reactの基礎 - Hooksの基礎]] * [[Reactの基礎 - useState]] * [[Reactの基礎 - useEffect]] * [[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,useRef,useState,useEffect,Hook,DOM,forwardRef,useImperativeHandle,RefObject,MutableRefObject,コールバックref |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の基礎 - useRef
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse