useRefのおさらい
Reactではレンダリング間で保持して欲しいが、レンダリングに関わらないような値を扱うときは、useRefというReact独自の関数から提供される値を利用します。
// コンポーネントのトップレベルで`useRef`を呼び出し、第一引数には初期値を渡す
// 型はジェネリクスで指定できる
const ref = useRef<number>(0);
// `ref`はオブジェクトでレンダリング時の値を`ref.current`にもつ
// ref.currentはミュータブルな値
console.log(ref.current);
例えばuseRefの値を用いたカウンターを作成することを考えてみます。useRefを用いた値はレンダリングに関わらないので、値自体の更新を行ってもレンダリングは発火せず更新後の値が描画されることはありません。
しかし、この値は異なるレンダリング間で値を保持する性質があるため、別の何かしらの原因で再レンダリングが発火し新たに画面が描画される時には更新後の値が参照されます。
実際にuseRefを用いたカウンターは以下のように動作します。useStateから提供される値を更新して再レンダリングを引き起こすとレンダリング前に更新してきた値が画面に描画されることが確認できます。
useRefとDOM操作
このような性質を持つuseRefですが、この値はDOMの操作によく使われます。
const SampleComponent: FC = () => {
const ref = useRef<HTMLInputElement>(null);
return <input ref={ref} />;
};
このように記述すると、画面に描画された時にref.currentにinputのDOMノードが設定されます(描画前はnullが設定されています)。設定したDOMノードを用いてその要素特有の操作や値を取得できます。
const handleFocus = () => {
if (!ref.current) {
return;
}
console.log(ref.current.labels);
ref.current.focus();
}
ref.cuurentから実行するメソッドや取得する値の種類はuseRefを呼び出す時に設定する型に依存します。
HTMLInputElementを渡した場合はinputで行える操作をref.currentを介して行えます。この場合はfocusメソッドを実行することやlabelsを取得することができます。これらの呼び出しはdivにはないものなのでHTMLDivElementを渡した場合には存在しないプロパティへのアクセスとなりエラーが発生します。
実際にrefを渡した要素と異なるインターフェイスを渡したとしてもエラーTypeScriptのエラーにはならず実行時に予期しない動きになる恐れがあるので正しいものを渡せていることに注意してください。
単にuseRefでDOM要素を扱うことを表したいときはHTMLElementを渡すこともできます。
ElementRef
DOM要素にref渡すときにuseRefに対する型付けはHTMLElementのようなインターフェイスを渡す他にもReact(@types/react)が提供するElementRefを用いて解決することができます。
const SampleComponent: FC = () => {
const ref = useRef<ElementRef<'input'>>(null);
return <input ref={ref} />;
};
ElementRefの型引数として対象の要素を文字列で渡すように型を指定して使います。
この型の引数には要素の文字列以外にもforwardRefなどを用いて作成したrefを渡すコンポーネントの型も渡せます。
これによってrefを別のコンポーネントに渡す場合にどの要素を渡せば良いか分からなくても、コンポーネントをもとに型を与えられます。
import { ElementRef, forwardRef, useRef } from "react";
const TextField = forwardRef<HTMLInputElement, { name: string }>(
({ name }, ref) => {
return <input type="text" name={name} ref={ref} />;
}
);
export default function App() {
const ref = useRef<ElementRef<typeof TextField>>(null);
return <TextField ref={ref} name="name" />;
}
この形式の型指定ができる点でElementRefはとても便利な型となります。