概要
- ref は、
- state と同様に、レンダー間で変数が維持される
- state と異なり、セットしても再レンダーがトリガされない
- 子コンポーネント内の DOM を操作したい場合、
useRef
とforwardRef
を使う
注意点
refオブジェクト.current
はコンポーネント本体に書いてはダメ。イベントハンドラやエフェクトから、読み書きする
落とし穴 < 使用法 < useRef – React
使い所
- コンポーネントに情報を「記憶」させたいが、その情報が新しいレンダーをトリガしないようにしたい場合(=レンダー時には不要な値を保持させたい場合)
⭐️ref は画面に表示したい情報を保存するのには適していない(ref と state の違い < ref で値を参照する – React) - DOM 操作
書き方
useRef
useRef ① 導入部
const ref = useRef(initialValue)
左辺:useRef が返す、
refオブジェクト
。以下の特徴を持つ- 唯一のプロパティであるcurrent(=
refオブジェクト.current
)に、指定された初期値が設定される refオブジェクト
を DOM ノードの JSX の ref 属性として渡すと、refオブジェクト.current
にDOMノードが設定され、その DOM ノードを操作できるようになる- 2 回目以降のレンダーで、
useRef
は同じオブジェクトを返す
- 唯一のプロパティであるcurrent(=
右辺:useRef
- 引数は、
refオブジェクト
のcurrent
プロパティ(=refオブジェクト.current
)の初期値として設定する値 - DOM 操作の場合、引数は null を設定する
- この引数は 2 回目以降のレンダーでは無視される
- 引数は、
書き方例( useRef ① 導入部)
import { useRef } from 'react'; function MyComponent() { const intervalRef = useRef(0); // DOM 操作の場合、初期値は null const inputRef = useRef(null); // ...
useRef ②-1 値の読み書き
refオブジェクト.current
の読み書き
書き方例( useRef ②-1 値の読み書き)
- 書き込み
function handleStartClick() { const intervalId = setInterval(() => { // ... }, 1000); intervalRef.current = intervalId; }
- 呼び出し
function handleStopClick() { const intervalId = intervalRef.current; clearInterval(intervalId); }
useRef ②-2 DOM 操作
- DOM の操作方法
- 操作したいDOMノードのJSXの
ref
属性に、refオブジェクト
を渡す refオブジェクト.current
にDOMノードが設定されるので、イベントハンドラやエフェクトに操作を記述- ノードが画面から削除されると、
refオブジェクト.current
はnull
になる
- 操作したいDOMノードのJSXの
書き方例( useRef ②-2 DOM 操作)
【<input>
を操作したい場合】
<input>
の ref 属性に refオブジェクト
を渡す
// ... return <input ref={inputRef} />;
この DOM ノードが生成され、画面に配置されると、refオブジェクト.current
にその DOM ノードが設定される(=その DOM ノードを操作できるようになる)。
これで、<input>
の DOM ノードにアクセスして、focus()
のようなメソッドを呼び出すことができる
function handleClick() { inputRef.current.focus(); }
forwardRef
const SomeComponent = forwardRef(render)
書き方例( forwardRef )
【親コンポーネントの Form
から、子の MyInput
内の <input>
を操作したい場合】
「子コンポーネント」の定義を forwardRef()
でラップ。
ref
を、公開したい DOM ノードに渡す。
import { forwardRef } from 'react'; const MyInput = forwardRef(function MyInput(props, ref) { const { label, ...otherProps } = props; return ( <label> {label} // 公開したい DOM ノードに ref を渡す <input {...otherProps} ref={ref} /> </label> ); });
これで、親の Form
コンポーネントが、MyInput
によって公開された <input>
DOM ノードにアクセスできるようになる。
function Form() { const ref = useRef(null); function handleClick() { ref.current.focus(); } return ( <form> <MyInput label="Enter your name:" ref={ref} /> <button type="button" onClick={handleClick}> Edit </button> </form> ); }
書き方例(useRef (① + ②-2) + forwardRef)
DOM操作例1:ボタンクリックで入力欄にフォーカス(関数宣言での書き方 + ファイル分け)
//親コンポーネントのファイル import { useRef } from 'react'; import HogeInput from './HogeInput.js'; import FugaButton from './FugaButton.js'; export default function Form() { // DOM 操作の場合、初期値は null const piyoRef = useRef(null); function handleClick() { piyoRef.current.focus(); } return ( <> <HogeInput ref={piyoRef} /> <FugaButton onClick={handleClick} /> </> ); }
// HogeInput.js import { forwardRef } from 'react'; export default forwardRef( function HogeInput(props, ref) { // 公開したい DOM ノードに ref を渡す return <input ref={ref} />; } );
// FugaButton.js export default function FugaButton({ onClick }) { return ( <> <button onClick={onClick}> ボタン </button> </> ); }
DOM操作例2:ボタンクリックで入力欄にフォーカス(関数式での書き方)
import { forwardRef, useRef } from 'react'; const HogeInput = forwardRef((props, ref) => { // 公開したい DOM ノードに ref を渡す return <input ref={ref} />; }); export default function Form() { // DOM 操作の場合、初期値は null const piyoRef = useRef(null); function handleClick() { piyoRef.current.focus(); } return ( <> <HogeInput ref={piyoRef} /> <button onClick={handleClick}> ボタン </button> </> ); }
参照
useRef – React
forwardRef – React
ref で値を参照する – React
ref で DOM を操作する – React