TypeScriptでrefを型定義する方法(useRef)を完全解説!初心者でもわかるReactの基本
生徒
「Reactでrefってよく聞きますけど、TypeScriptを使うときは型をどうやって書くんですか?」
先生
「refはコンポーネント内で直接DOM要素や値にアクセスするために使います。TypeScriptでは型をしっかり付けて使うと安全ですよ。」
生徒
「DOM要素って何ですか?」
先生
「DOM要素とは、HTMLのタグそのものです。例えば<input>や<div>のことを指します。」
生徒
「なるほど!じゃあTypeScriptでrefを型安全に書く方法を知りたいです!」
先生
「それでは具体的にコードを使って解説していきましょう。」
1. useRefの基本的な使い方
useRefは、Reactで「参照」を保持するために使うフックです。参照とは、特定の要素や値を記憶しておく箱のようなものです。useRefでよく使うのが、フォームの入力欄(inputタグ)にアクセスするときです。
import React, { useRef } from "react";
const InputFocus: React.FC = () => {
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleClick}>フォーカスを当てる</button>
</div>
);
};
export default InputFocus;
この例ではuseRef<HTMLInputElement>(null)と書いて、refの型を「HTMLのinput要素」と定義しています。これにより、TypeScriptがinputRef.currentを使うときに正しく補完してくれるようになります。
2. 数値や文字列の値を保持する場合
useRefはDOM要素だけでなく、単純な値を保持することもできます。例えば数値や文字列を保存しておきたいときに便利です。
import React, { useRef } from "react";
const Counter: React.FC = () => {
const countRef = useRef<number>(0);
const handleIncrement = () => {
countRef.current += 1;
console.log("現在の値:", countRef.current);
};
return (
<div>
<button onClick={handleIncrement}>カウントアップ</button>
</div>
);
};
export default Counter;
ここではuseRef<number>(0)と書くことで、数値専用の箱を作っています。これにより、誤って文字列を代入しようとするとTypeScriptがエラーを出してくれるので安心です。
3. 型を省略するとどうなる?
もしuseRef(null)とだけ書くと、型はMutableRefObject<undefined>として推論されてしまい、実際のDOM操作ではエラーになる可能性があります。そのため、基本的には必ず型を明示して書くことをおすすめします。
const inputRef = useRef<HTMLInputElement | null>(null);
このように「| null」を付けることで、「まだ要素が存在しないかもしれない」という可能性も正しく表現できます。
4. refを使うときの注意点
初心者がよく間違えるポイントを整理しておきましょう。
- useRefは直接画面に表示されるものではなく、裏で参照を保持するためのもの
- DOM要素に使うときは必ず適切な型(例:
HTMLDivElement、HTMLInputElement)を指定する - 数値や文字列を保持するときはその型(例:
number、string)を指定する - refを使わずに済む場合はstateを優先するのが基本
例えば、「フォームの入力内容を取得して表示したい」というときはstateを使いますが、「ボタンを押したら入力欄にフォーカスを当てたい」というときはrefを使うのが適切です。
5. よく使うDOM要素と型の一覧
最後に、ReactとTypeScriptでrefを使うときによく登場するDOM要素と型の対応表を載せておきます。
<input>→HTMLInputElement<textarea>→HTMLTextAreaElement<button>→HTMLButtonElement<div>→HTMLDivElement<form>→HTMLFormElement
これらを覚えておくと、refの型を付けるときにすぐに書けるようになります。
まとめ
この記事では、React開発における重要なフックの一つであるuseRefを、TypeScriptで型安全に活用する方法について詳しく解説してきました。ReactとTypeScriptを組み合わせて使用する際、useRefの型定義を正しく行うことは、実行時の予期せぬエラーを防ぎ、エディタの強力な補完機能を最大限に引き出すために不可欠なスキルです。
useRefは、大きく分けて二つの役割を持っています。一つは、特定のDOM要素(inputタグやdivタグなど)に直接アクセスして、フォーカス制御やスクロール位置の取得などを行う役割です。もう一つは、レンダリングを発生させずに、コンポーネントの再描画後も保持し続けたい「書き換え可能な値」を管理する役割です。これらを用途に合わせて正しく使い分けることが、クリーンなコードを書くための第一歩となります。
TypeScriptでのuseRefの型定義パターン
実務でよく使われる、より実践的なuseRefの型定義の書き方を復習しましょう。特にDOM要素を操作する場合は、初期値にnullを渡し、ジェネリクスを使用してHTML要素の型を指定するのが標準的な書き方です。
import React, { useRef, useEffect } from "react";
const VideoPlayer: React.FC = () => {
// ビデオ要素への参照を型定義(HTMLVideoElement)
const videoRef = useRef<HTMLVideoElement>(null);
const handlePlay = () => {
// currentが存在するかチェック(オプショナルチェイニング)
videoRef.current?.play();
};
const handlePause = () => {
if (videoRef.current) {
videoRef.current.pause();
}
};
return (
);
};
export default VideoPlayer;
なぜ型定義が必要なのか:SEOと保守性の観点から
TypeScriptで厳格に型を定義することは、単にエラーを防ぐだけではありません。大規模なプロジェクトにおいて、どのコンポーネントがどのDOM要素を操作しているのかを明示的にすることで、コードの可読性が飛躍的に向上します。これは開発効率を高めるだけでなく、結果として質の高いWebサイトを素早く提供することに繋がり、間接的にSEO(検索エンジン最適化)の成果にも寄与します。
また、検索エンジンに評価されるアクセシビリティの高いサイトを作るためには、適切なタイミングでフォーカス制御を行うことが求められます。例えば、モーダルウィンドウを開いた際に入力項目へ自動でフォーカスを移すといった処理は、useRefとTypeScriptを組み合わせることで、論理的なミスなく実装することが可能になります。
DOM操作以外のuseRef活用法
useRefは「値の保持」にも非常に有効です。useStateを使うと値が更新されるたびにコンポーネントが再レンダリングされますが、useRefは値を更新しても再レンダリングが発生しません。この特性を利用して、以前のpropsの値を保持したり、タイマーのID(setIntervalの戻り値)を保存したりする場合によく使われます。
import React, { useRef, useState } from "react";
const TimerApp: React.FC = () => {
const [seconds, setSeconds] = useState<number>(0);
// タイマーIDを保持するためのref。初期値はnull、型はNodeJS.Timeoutまたはnumber
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
const startTimer = () => {
if (timerRef.current !== null) return; // 二重起動防止
timerRef.current = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
};
const stopTimer = () => {
if (timerRef.current !== null) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};
return (
経過時間: {seconds}秒
);
};
export default TimerApp;
学習のポイント振り返り
最後に、初心者がuseRefとTypeScriptを扱う際に意識すべきポイントをまとめます。第一に、ジェネリクス(< >)の中には対象となるHTML要素の正確な型を入れること。第二に、初期値にはnullを指定し、使用時には必ずnullチェックを行うこと。第三に、何でもrefで解決しようとせず、データの同期が必要な場合はuseStateを使用するという原則を忘れないことです。
TypeScriptの学習は最初は難しく感じるかもしれませんが、一度型定義の便利さを知れば、型のないJavaScriptでの開発には戻れなくなるほど強力なツールです。今回学んだ基礎を土台にして、さらに高度なReact Hooksやライブラリの活用に挑戦していきましょう。
生徒
「先生、ありがとうございました!useRefに型を付ける方法がようやくスッキリ理解できました。今までなんとなくanyを使ってしまっていた部分もあったので、これからはちゃんとHTMLInputElementとかを指定するようにします。」
先生
「それは素晴らしい進歩ですね。anyを使ってしまうとTypeScriptの良さが消えてしまいますから。特にDOM要素は種類が多いので、この記事にある対応表を参考に、正確な型を付ける癖をつけましょう。」
生徒
「はい!あと、currentの後にハテナ(?.)を付けてアクセスする方法も便利だなと思いました。nullチェックを一行で書けるんですね。」
先生
「そう、オプショナルチェイニングですね。TypeScriptでは初期値がnullである以上、要素が存在しない可能性を常に考慮しなければなりません。ハテナを使うことで、コードを短く保ちつつ安全にプロパティにアクセスできるようになります。」
生徒
「なるほど。ところで、useRefで値を保存するときに、いつその値が書き換わったのかを画面に表示させることはできないんですか?」
先生
「いい質問です。そこがuseRefとuseStateの決定的な違いです。useRefの値を書き換えても画面は再描画されません。もし画面に反映させたいならuseStateを、裏側でこっそり値を保持したいならuseRefを選んでください。これがReactの設計思想の肝になります。」
生徒
「『裏側でこっそり』ですね!使い分けの基準がよく分かりました。まずはフォームの自動フォーカス機能から自作のコンポーネントに組み込んでみようと思います!」
先生
「その意気です。実際に手を動かしてコードを書くことで、型の知識が血肉になります。何かわからないことがあれば、またいつでも聞いてくださいね。応援していますよ!」