ReactとTypeScriptの使い方がまとめられた「React + TypeScript Cheatsheets」の「useRef」の情報にもとづいて、このフックの型づけと初期値の与え方について3つの定めを解説します。useRefフックの型には、試しやすいようにnumberを用いました。
const nullRef = useRef<number>(null);
const nonNullRef = useRef<number>(null!);
const nullableRef = useRef<number | null>(null);
nullで初期化したとき
useRefフックの初期値にnullを与えると、戻り値のrefオブジェクトは読み取り専用です。つまり、currentプロパティは書き替えられません。
// 読み取り専用。
const nullRef = useRef<number>(null);
// 読み取り専用プロパティであるため、'current' に代入することはできません。
// nullRef.current = 1;
読み取り専用にする典型は、useRefをHTML要素(HTMLElement)で型づけし、要素のref属性に与える場合です(「useRef」参照)。このとき、currentプロパティは書き替えないでしょう。
たとえば、つぎのようにuseRefにnullを渡してHTMLDivElementで型づけると、そのcurrentプロパティ(div)はHTMLDivElement | nullのユニオン型で推論されます。すると、要素の参照として扱う前に、必ずnullでないことを確かめなければなりません。そうしなければ、つぎのようなエラーが示されてしまうのです。
オブジェクトは 'null' である可能性があります。
function App() {
const divRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const div = divRef.current;
if (!div) throw Error("divRef is not assigned"); // nullでないことを確かめる
console.log(div.clientWidth);
}, []);
return (
<div ref={divRef} />
);
}
useRefの引数に定めるHTML要素の型は、refを与える要素に応じて適切に絞り込んでください。あえて広めの型を定めると、エラーメッセージから適切な型が示されることもあります。
function App() {
// const inputRef = useRef<HTMLInputElement>(null); // ←正しくは
const inputRef = useRef<HTMLElement>(null);
return (
/* 型 'RefObject<HTMLElement>' を型 'LegacyRef<HTMLInputElement> | undefined' に割り当てることはできません。
型 'RefObject<HTMLElement>' を型 'RefObject<HTMLInputElement>' に割り当てることはできません。
型 'HTMLElement' には 型 'HTMLInputElement' からの次のプロパティがありません: accept, align, alt, autocomplete、49 など。 */
<input ref={inputRef} type="text" />
);
}
初期値nullに非nullアサーション演算子!を添える
初期値のnullに非nullアサーション演算子(non-null assertion operator)!を添えてnull!とすると、今度はrefオブジェクトのcurrentプロパティが書き替えられます。もちろん、代入できるのは指定した型のみで、nullは入れられません。
// 指定した型の値で書き替えられる。nullは不可。
const nonNullRef = useRef<number>(null!);
console.log(nonNullRef.current); // null
// 型 'null' を型 'number' に割り当てることはできません。
// nonNullRef.current = null;
nullableRef.current = 1;
console.log(nullableRef.current); // 1
nullでないことを型アサーションすれば、要素のrefに定めたときも、参照する前にnullかどうか確かめる必要がありません。ただし、nullでないことは、コードを書く側に責任が委ねられます。たとえば、要素にrefが与えられていないなど、参照が正しく得られなければ、ランタイムエラーになるでしょう。
function App() {
const divRef = useRef<HTMLDivElement>(null!);
useEffect(() => {
const div = divRef.current;
// nullでないことは確かめなくてよい
console.log(div.clientWidth);
}, []);
return (
<div ref={divRef} />
);
}
useRefフックの役割は、refオブジェクトを要素のref属性に与えることだけではありません(「React: useRefフックで状態変数のひとつ前の値や最新の値を得る」参照)。初期値はnullとしつつ、currentプロパティを書き替えたい場合に用います。
型指定にnullを含める
useRefフックに書き替え可能の初期値nullを渡したうえで、あえてnullも代入値に含めたいときは、型指定にユニオン型(union type)|で加えます。
// 型指定にnullを含める。
const nullableRef = useRef<number | null>(null);
nullableRef.current = 1;
console.log(nullableRef.current); // 1
nullableRef.current = null;
console.log(nullableRef.current); // null
まとめ
useRefの3つの型指定と初期値の与え方で、currentプロパティの扱いが少しずつ違ってくるということです。それらを意識して、用途に応じて使いましょう。
// 読み取り専用。
const nullRef = useRef<number>(null);
// 指定した型の値で書き替えられる。nullは不可。
const nonNullRef = useRef<number>(null!);
// 型指定にnullを含める。
const nullableRef = useRef<number | null>(null);