TypeScriptでReact Queryをマスター!初心者向け型安全データフェッチ完全ガイド
生徒
「Reactでインターネットからデータを取得するとき、型安全にスマートに処理する方法はありますか?」
先生
「それなら、TypeScriptとReact Query(TanStack Query)を組み合わせるのが最適です。データの取得や管理が劇的に楽になりますよ。」
生徒
「React Queryを使うと、普通のfetchと何が違うんですか?」
先生
「データの再取得やキャッシュの管理を自動で行ってくれます。さらにTypeScriptを使えば、どんなデータが返ってくるかを厳密に定義できるので、バグを未然に防げます。具体的な使い方を一緒に学んでいきましょう!」
1. React Queryと型安全なデータフェッチとは?
現代のフロントエンド開発、特にReactやNext.jsを使った開発において、外部のサーバーからデータを取得する「データフェッチ」は欠かせない要素です。通常、JavaScriptのfetch関数などを使ってデータを取得しますが、それだけでは「通信中」「エラー発生」「データの保存(キャッシュ)」などの管理が非常に大変です。
ここで登場するのがReact Query(リアクトクエリ)です。これは、非同期データの管理を専門に行うライブラリで、複雑な処理をシンプルなコードで記述できるようにしてくれます。そして、ここにTypeScriptを組み合わせることで、「どんな形式のデータが届くのか」をプログラムに教えることができます。これを型安全(かたあんぜん)と呼びます。型が安全であるということは、届くはずのないデータを操作しようとしたときに、パソコンが事前に「それは間違いですよ」と教えてくれる状態を指します。
プログラミング未経験の方にとって「型」は難しく感じるかもしれませんが、料理に例えると分かりやすいです。「カレーを作る」という目的(関数)に対して、材料が「肉と野菜(型)」であることを指定しておくようなものです。もし材料に「石ころ」が混ざろうとしたら、TypeScriptがすぐに止めてくれる。これが型安全のメリットです。
2. データの形を定義するInterfaceとType
TypeScriptでデータフェッチを行う第一歩は、取得したいデータの「設計図」を作ることです。これを「インターフェース(Interface)」や「型(Type)」と呼びます。例えば、ユーザー情報を取得する場合、名前、年齢、メールアドレスといった項目がどのような種類(文字列なのか数字なのか)であるかを定義します。
パソコンに慣れていない方のために説明すると、これは名簿の項目欄を作る作業に似ています。「氏名」の欄には文字を書き、「年齢」の欄には数字を書くというルールを決めておくのです。このルールがあるおかげで、後からプログラムが「年齢に名前が書いてある!」といった混乱を起こさずに済みます。まずは、シンプルなユーザー情報の型を定義してみましょう。
// ユーザー情報の型を定義します
interface User {
id: number; // IDは数字
name: string; // 名前は文字列
email: string; // メールアドレスは文字列
}
// この型を使って、特定のユーザーデータを作成する例
const sampleUser: User = {
id: 1,
name: "山田太郎",
email: "yamada@example.com"
};
このように定義しておくことで、sampleUser.nameは必ず文字列であることが保証されます。もし間違えてsampleUser.name = 100のように数字を入れようとすると、TypeScriptがエラーを出してくれます。
3. useQueryを使った基本的なデータ取得
React Queryのメイン機能であるuseQueryというフックを使って、実際にデータを取得してみましょう。フックとは、Reactの中で特定の機能を使うための便利な道具のことだと思ってください。useQueryには、「データの識別名(クエリキー)」と「データを取得する関数」を渡します。
ここで重要なのが、useQuery<T>という書き方です。この<T>の部分に先ほど作った型を入れることで、取得されるデータがその型に従っていることをReact Queryに伝えます。これにより、取得したデータを使うときに「このデータには何が入っているんだっけ?」と悩む必要がなくなります。検索エンジン最適化(SEO)の観点からも、構造化されたデータフェッチはコードの可読性を高め、メンテナンス性を向上させます。
import { useQuery } from '@tanstack/react-query';
// データを取得する関数(シミュレーション)
const fetchUser = async (): Promise<User> => {
const response = await fetch('https://api.example.com/user/1');
if (!response.ok) {
throw new Error('データの取得に失敗しました');
}
return response.json();
};
// コンポーネント内での使用例
const UserProfile = () => {
// useQueryに型を指定して呼び出す
const { data, isLoading, error } = useQuery<User>({
queryKey: ['user', 1],
queryFn: fetchUser,
});
if (isLoading) return <div>読み込み中...</div>;
if (error) return <div>エラーが発生しました</div>;
return (
<div>
<h1>{data?.name}</h1>
<p>{data?.email}</p>
</div>
);
};
実行結果は以下のようになります。
読み込み中...
(通信完了後)
山田太郎
yamada@example.com
4. 非同期処理とPromiseの仕組み
データフェッチを学ぶ上で避けて通れないのが「非同期処理(ひどうきしょり)」です。これは、簡単に言うと「待ち時間が発生する作業」のことです。インターネットを通じてデータを取ってくるには、ほんの少し時間がかかります。その間、プログラムが完全に止まってしまうと、画面がフリーズしてしまいます。
それを防ぐのがasyncとawaitという魔法の言葉です。asyncは「この関数は時間がかかる作業を含みますよ」という宣言で、awaitは「データが届くまでここで少し待っててね」という合図です。TypeScriptでは、この「未来に返ってくる予定のデータ」をPromiseという型で表現します。初心者のうちは、「Promiseは予約チケットのようなもの」と考えると分かりやすいでしょう。今はチケットしかなくても、時間が経てば本物のデータと交換できるのです。
5. リストデータの取得と型定義の応用
単一のデータだけでなく、複数のデータ(リストや配列)を取得する場合もよくあります。例えば、ブログの記事一覧や商品リストなどです。この場合、型の定義に[](配列記号)を組み合わせます。TypeScriptを使うことで、配列の中身がすべて同じ型であることを保証できます。
複数のデータを扱う際は、ループ処理(map関数など)を使って画面に表示させますが、型が決まっていると、ループの中でどの項目が使えるかが自動的に表示される(補完機能)ため、開発のスピードが格段に上がります。これはパソコン操作に不慣れな方でも、入力ミスを減らせる大きな助けになります。
interface Post {
id: number;
title: string;
body: string;
}
const fetchPosts = async (): Promise<Post[]> => {
const response = await fetch('https://api.example.com/posts');
return response.json();
};
const PostList = () => {
const { data: posts, isLoading } = useQuery<Post[]>({
queryKey: ['posts'],
queryFn: fetchPosts,
});
if (isLoading) return <div>記事を読み込んでいます...</div>;
return (
<ul>
{posts?.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
};
このコードでは、postsがPost型の配列であることが確定しているため、post.titleを記述する際に、スペルミスがあればすぐに気づくことができます。
6. エラーハンドリングと例外処理の重要性
インターネットの世界では、常にスムーズにデータが取得できるとは限りません。電波が悪かったり、サーバーがメンテナンス中だったりすることもあります。このようなトラブルに対応することをエラーハンドリングと呼びます。
React Queryは、エラーが発生したかどうかも自動的に教えてくれます。TypeScriptを使っていれば、エラーオブジェクトの中身を詳しく調べることも可能です。プログラムが途中で止まって真っ白な画面にならないよう、「エラーが起きたときはこのメッセージを出す」という準備をしておくことが、親切なアプリケーション作りへの第一歩です。具体的には、error変数をチェックして、適切な表示をユーザーに届けます。
7. APIレスポンスに合わせた柔軟な型設計
実際の開発では、APIから返ってくるデータの形が複雑なこともあります。データがネスト(入れ子)になっていたり、オプション項目(あったりなかったりする項目)があったりする場合です。TypeScriptでは、?マークを使って「この項目は空っぽかもしれない」という設定ができます。
これにより、データが存在しないときに無理やり読み込もうとしてエラーになるのを防げます。これをオプショナルチェイニングと呼びます。専門用語が多くて大変かもしれませんが、要は「もしデータがあれば表示してね」という控えめな命令のことです。次のコードで、もう少し複雑な型定義を見てみましょう。
interface Product {
id: string;
name: string;
price: number;
description?: string; // 説明文は無いかもしれないので ? をつける
category: {
id: number;
label: string;
};
}
const ProductCard = ({ product }: { product: Product }) => {
return (
<div className="card">
<h3>{product.name}</h3>
<p>価格: {product.price}円</p>
{/* descriptionがあるときだけ表示される */}
{product.description && <p>{product.description}</p>}
<span className="badge">{product.category.label}</span>
</div>
);
};
8. React QueryをNext.jsで活用するメリット
React Queryは、Next.jsというフレームワークとも非常に相性が良いです。Next.jsはSEOに強いという特徴がありますが、React Queryを使うことでクライアントサイド(ブラウザ側)でのデータ更新がとてもスムーズになります。例えば、ユーザーがボタンを押した瞬間にデータを最新に更新したり、一度見たページに戻ったときに一瞬でデータを表示したりすることが可能です。
これは、React Queryが「キャッシュ」という仕組みを持っているからです。キャッシュとは、一度取得したデータを一時的に手元に保存しておくメモ書きのようなものです。何度も同じデータをサーバーに取りに行かなくて済むため、動作がキビキビとした快適なウェブサイトになります。TypeScriptで型を整えておけば、このキャッシュから取り出したデータも常に安全に扱うことができます。
9. 開発効率を最大化する自動生成ツールの紹介
最後に、さらにステップアップしたい方向けに、型の定義を自動で行う方法についても触れておきます。大規模な開発では、数百種類もの型を手書きするのは大変です。そこで、APIの仕様書(Swaggerなど)からTypeScriptの型を自動で作り出すツールが存在します。
初心者の方は、まずは自分で型を書くことから始めて、仕組みを理解することが大切です。しかし、将来的に仕事でプログラムを書くようになれば、こうしたツールを使って、より効率的で間違いのない開発を行うことになります。TypeScriptとReact Queryの組み合わせは、まさに現代のプロフェッショナルな現場で標準となっている強力なタッグなのです。