useRefの基本的な使い方
コンポーネントに ref を追加するには、コンポーネント内で、useRef
フックを呼び出し、第一引数に参照したい初期値を渡します。
変数 ref は意図的に書き換えが可能な変数となっています。
const ref = useRef(0);
このように宣言した変数 ref は、中身は以下のようなオブジェクトとなり、以下のように参照できます。
{
current: 0
}
ref.current; // 0
ref.current = 2; // 2
また、useRef
の型引数に追加したい値の型を指定できます。
const countRef = useRef<number>(0); // number型
const idRef = useRef<string>(""); // string型
const inputRef = useRef<HTMLInputElement>(null); // inputタグのrefに代入できる
値を保持・参照する
useRef
によって生成された変数は、その変数に変化があっても再レンダーは走らず、また、コンポーネントに再レンダーが走った時にはその値を保持し続けます。
let変数・state変数との違い
let
による変数定義でも書き換え可能な変数を作成できますが、useRef
によって作成した変数との違いは、コンポーネントの再レンダーによって初期化されるかがあります。
useState
によって宣言した変数は set 関数でしか書き換えができません。また、state 変数に変更があればコンポーネントには再レンダーが走ります。
export const Counter: React.FC = () => {
let count = 0;
const incrementLet = () => {
count = count + 1;
console.log(count, "let変数");
};
const [countState, setCountState] = useState(0);
const incrementCountState = () => {
setCountState(countState + 1);
console.log(countState, "state変数");
};
const countRef = useRef<number>(0);
const incrementCountRef = () => {
countRef.current = countRef.current + 1;
console.log(countRef.current, "useRef変数");
};
return (
<div>
<p>{count}</p>
<button onClick={incrementLet}>INCREMENT</button>
<p>{countState}</p>
<button onClick={incrementCountState}>INCREMENT</button>
<p>{countRef.current}</p>
<button onClick={incrementCountRef}>INCREMENT</button>
</div>
);
};
上のコードでは let
、useState
、useRef
によって宣言した変数をそれぞれに対応するボタンのクリックでインクリメントします。
このコードの場合、ボタンのクリックによってブラウザ上の見た目が変わるのは、useState
で宣言した変数をインクリメントした時だけです。なぜなら state 変数に変化が生じたことでコンポーネントが再レンダリングされたからです。
let
、useRef
で宣言した変数は内部的にはインクリメントしていることがログからわかります。
2 つの違いとしては、state 変数をインクリメントした後で let
で宣言した変数は値が 0 になりますが、useRef
での場合再レンダー前の値を引き継ぎます。
React のドキュメントには レンダー中に current の値を読み取る(または書き込む)べきではない
とあります。
上記のコードでは countRef.current
を画面に表示していますが、本来であれば画面に表示させるべきではありません。
レンダー中に書き込み・読み込みをしてはいけないので、代わりにイベントハンドラから読み書きをします。
function MyComponent() {
// ...
useEffect(() => {
// ✅ You can read or write refs in effects
myRef.current = 123;
});
// ...
function handleClick() {
// ✅ You can read or write refs in event handlers
doSomething(myOtherRef.current);
}
// ...
}
再描画を抑制する
input タグに ref を渡すことで入力フォームの入力時の再レンダリングを抑制できます。
このようにフォームの入力値を DOM で扱うコンポーネントを 非制御コンポーネント
といいます。
const UncontrolledForm: React.FC = () => {
const inputRef = useRef<HTMLInputElement>(null);
return (
<div>
<input ref={inputRef} />
<button
onClick={() => {
console.log(inputRef.current.value);
}}
>
click
</button>
</div>
);
};
input タグに ref を渡すので useRef
の型引数を HTMLInputElement
にします。
入力した値は inputRef.current.value
で参照できます。
DOMを操作する
React で操作できない useRef
で場合 DOM API を使って操作できます。
例:
- フォーカスを当てる
- スクロールする
- サイズ位置を測定する
ボタンのクリックで入力フォームにフォーカスを当てる例のコードを示します。
const Component: React.FC = () => {
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
inputRef.current?.focus();
};
return (
<div>
<button onClick={handleClick}>フォーカスを当てる</button>
<input ref={inputRef} />
</div>
);
};
forwardRef
基本的に useRef
によって宣言した変数は自コンポーネントのみで利用でき、子コンポーネントに渡すには明示的にコンポーネントを forwardRef
でラップする必要があります。
const MyInput = forwardRef<HTMLInputElement, ComponentPropsWithRef<"input">>(
(props, ref) => <input type="text" {...props} ref={ref} />
);
const Component: React.FC = () => {
const inputRef = useRef<HTMLInputElement>(null);
return (
<div>
<MyInput ref={inputRef} />
</div>
);
};