はじめに
以前useRef
の2つの使い方について記事にまとめたが、useRef
をTypeScript
のもとで使う際には、2つの使い方それぞれで必要な型定義が異なることを学んだので、今回は型の使い分けについてまとめる。
useRefの2つの使い方
まずはuseRefの2つの使い方を簡単におさらいする。
使用方法① : DOMへのアクセス
- 例えば のinput要素にフォーカスしたい場合、
JavaScript
でdocument.getElementById('name').focus()
というように書くが、これと同じことをReact
で行いたい場合にuseRef
を使ってDOMへアクセスすることができる。 - hooks誕生以前からの
ref
の使い方。
使用方法② : 値を保持する
-
useState
も値を保持することが出来るが、useState
と違いuseRef
では値を更新してもコンポーネントの再レンダリングが起きないというのが特徴。 - hooks誕生により新たにできた
ref
の使い方。
useRefの返り値
では2種類の使い方を確認したところで型の話に入るが、実はuseRef
の返り値にも RefObject
もしくはMutableRefObject
という2種類の型がある。
RefObject
- hooks登場以前からのRefObject。
- .currentの初期値はnullであり、.currentに
readonly
の制約が付いている。
MutableRefObject
- hooksによりできた値を保持するという用例に対応するため、
readonly
の制約がないMutableRefObjectが設けられた。
返り値の型の使い分け
上記の特徴を見ると察することができる通り、DOMへのアクセスの為(従来からの用例)に使う場合は RefObject
型を使い、値の保持の為(hooks以後の用例)に使う場合は readonly
制約のない MutableRefObject
型を使う。
そしてこれら返り値の型は初期値と引数の与え方によって決まるので、使用方法に合わせた初期値と引数を与える必要がある。
DOMへのアクセスに使う場合の型付け
DOMへのアクセスの為にuseRef
を使う場合は、返り値を RefObject
型にする必要がある。
どうすれば RefObject
型になるのかというと、初期値がnull かつ 型定義にnullを含まない ようにすると、返り値は RefObject
型となる。
よってDOMへのアクセスに使う場合は、以下のようにする。
- 初期値はnullを与える
- 型引数は、中身に入りうるDOMノードもしくはReact要素を与える
function Home() {
const ref = useRef<HTMLInputElement>(null)
return <input ref={ref}>Home</input>
}
仮に上記の条件を満たしておらず返り値が MutableRefObject
型となった場合、ref属性に設定する箇所でTypeScriptのエラーが出る。
function Home() {
const ref = useRef<HTMLInputElement>() // MutableRefObject型になる
return <input ref={ref}>Home</input> // エラー
}
値の保持に使う場合の型付け
値の保持に使う場合のは返り値を MutableRefObject
型にする必要があるが、上記の RefObject型
の条件に当てはまらない場合は、全てMutableRefObject
型となる。
なので通常のTypeScriptのセオリー通り、なるべく初期値を与え、型引数は初期値からの推論に任せるか明示的に書くと良い。
// 推論されて `MutableRefObject<number>` になる。
const ref = useRef(0)
// `MutableRefObject<string | null>` になる。
const ref = useRef<string | null>(null)
仮に RefObject型
なのにcurrent
プロパティに値を代入しようとすると、「読み取り専用プロパティであるため、'current' に代入することはできません。」というエラーが出る。
さいごに
TypeScript
を使っていると、思わぬところで躓き時間がかかることがある。
今回は返り値の型の違いを知ったことでuseRef
についての理解も深まったので、今後もその場しのぎでTypeScrpt
のエラーが消えればOKではなくしっかり理解してTypeScript
を活用していきたい。
参考記事