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);