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
はとても便利な型となります。