React.useRef
コンポーネントの ref に渡せばDOMへの参照が得られるよ code:jsx
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onClick = () => {
// current points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onClick}>Focus the input</button>
</>
);
}
もちろん ref に渡せばDOMの参照が得られるのだが、実は useRef はただのハコ (plain JS Object)を召喚するだけ。
useRef で召喚した hoge の hoge.current はmutableであり、なんでも置いておくことができる。
ref に渡したときはReactくんが hoge.current にDOM nodeを自動的にセットしている。
重要な違いが、 useRef のハコの中身 hoge.current をmutateしても re-renderが起らないこと。
ということでいろんな使い方ができちゃうよ。
レンダリングを抑えた"変数"として使う
ハコに変数をいれてるだけ。
メンバ変数とおんなじような感じ。
レンダリング後も値を保持してるって意味で useState と同じだよ
※ useState はre-renderされるが、さっきも書いたけど useRef はre-renderされないよ
code:jsx
import React, { useRef, useState } from 'react'
const App = () => {
const valueRef = useRef();
const onClick = e => {
setValue(valueRef.current.value)
}
return (
<>
<h4>値だよ: {value}</h4>
<input ref={valueRef}>
<Button onClick={onClick}>ボタンだよ</Button>
</>
);
}
<input value={value}> として直接stateの value を見てしまうとしたら
1. input するたびにvalueが書き換わり
2. value stateを持っている <App> までre-render されちゃうよ(もちろんボタンもね)
<input ref={valueRef}> とすることで
inputするたびにstateが書き換わらない!!!
クリックによってstateが書き換わって全体がre-render
となって平和に!
useRef + useEffect = "Callback Refs"
そういうわけで useRef のハコはre-renderされないので自分で変更タイミングを知りたくなる
<Button onClick /> のように特定のインタラクションと密に結合していればよいが、そうでない場合や普通にDOMへの参照として使っている場合は useEffect で変更を検知したくなる
そんなときは Callback Refs を使いましょう
refにコールバックを渡すと、そのコールバックはDOM or React Componentを引数として受け取るようになって弄れるようになるよ
つまり:useRef のハコを useEffect して変更検知したいなら、refにコールバックを渡せばコールバック内でDOMやComponentが得られるのだからそっちで検知しましょう!という話。
※ useCallback や useEffect のdependencyにハコを渡すこと自体は普通にできるし必要なケースもあるかもね
code:jsx
import React, { useCallback } from 'react'
const App = () => {
const onSomething = useCallback((e) => {
// e: HTMLInputElement が入るので好きにすればいい
// もっともinputならButtonのonClickみたいなイベントハンドラを使うのが普通だろうしDOMに触る必要はほぼないだろう // divの位置を取ってスクロールしたい!とかで使えるかもね!
}, []);
return (
<>
<h4>ただのサンプル</h4>
<input ref={onSomething}>
<Button onClick={onClick}>ボタンだよ</Button>
</>
);
}
前の状態を保っておきたい
そんなケースにも使えるよ
コード引用
code:tsx
function Counter() {
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
なぜこれで前の状態を保っていられるのか?
その理由は
まず、前述の通りref オブジェクトは明示的にアップデートしない限り同じ値を保持する
prevCountRef は undefined で初期化されており
useEffect がレンダリング後に非同期で行われるから。
useEffect はDOM更新 = レンダリングの"副作用" として行わせたい処理を書く