React useRefにTypeScriptで正しく型を指定する方法
問題
ReactでTypeScriptと一緒にuseRefを使うと、このようなエラーに遭遇することがあります。
const inputRef = useRef<HTMLInputElement>();
inputRef.current.focus();
// 'inputRef.current' is possibly 'undefined'.
初期値をnullにしても、別のエラーが発生します。
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current.focus();
// 'inputRef.current' is possibly 'null'.
毎回if (inputRef.current)でチェックしなければならないのかと思いますが、実はuseRefのオーバーロードを理解すれば、すっきり対処できます。
解決方法
useRefには2つのオーバーロードがあります。
// 1. DOM ref用(読み取り専用の.current)
// ジェネリックにnullを含め、初期値はnull
const inputRef = useRef<HTMLInputElement>(null);
// 型: RefObject<HTMLInputElement> — currentはreadonly
// 2. 値保存用(変更可能な.current)
// 初期値の型がジェネリックと一致
const countRef = useRef<number>(0);
// 型: MutableRefObject<number> — currentは変更可能
DOM refはReactが管理するのでreadonlyで、値保存用は自分で変更するのでmutableです。
実務でよく使うパターンを紹介します。
// DOM要素の参照
const inputRef = useRef<HTMLInputElement>(null);
const divRef = useRef<HTMLDivElement>(null);
// タイマーID保存
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
// 前回の値を追跡
const prevValueRef = useRef<string>("");
// マウント状態の追跡
const isMountedRef = useRef<boolean>(false);
イベントハンドラ内では要素が確実にマウントされているので、non-nullアサーションを使っても問題ありません。
const handleSubmit = () => {
inputRef.current!.focus();
};
ただしuseEffect内では、条件チェックをする方が安全です。
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
ポイント
useRef<T>(null)→RefObject<T>(DOM ref用、currentはreadonly)useRef<T>(initialValue)→MutableRefObject<T>(値保存用、currentは変更可能)- DOM要素のrefは常に
nullで初期化し、使用時にnullチェックを行います - タイマー、前回の値、外部ライブラリのインスタンスなどにはMutableRefObjectを使います