ReactでFetch APIのレスポンスをキャッシュする方法を解説!初心者向けにやさしく丁寧に紹介
生徒
「ReactでAPIからデータを取得しているんですが、同じデータを何度も取りに行っていて、ちょっと無駄かなと思ってます…」
先生
「それなら“キャッシュ”を使ってみるといいですよ。取得したデータを一時的に保存して、次回からは保存したものを使うようにできます。」
生徒
「キャッシュって聞いたことあるけど、Reactでも簡単にできるんですか?」
先生
「はい、ReactとFetch APIを使って簡単にレスポンスをキャッシュできますよ。詳しく説明していきましょう!」
1. キャッシュとは何か?
キャッシュとは、一度取得したデータを一時的に保存しておく仕組みのことです。たとえば、同じAPIを何度も呼び出すと、サーバーにも負荷がかかりますし、通信時間もかかってしまいます。
そこで、一度取得したデータをブラウザのメモリやストレージに保存しておくことで、次回はその保存済みデータを使って表示を速くすることができます。
2. ReactでFetch APIを使うとは?
Reactでは、JavaScriptのFetch APIを使ってサーバーからデータを取得するのが一般的です。たとえば、天気情報や商品リストなどを外部APIから読み込むことがあります。
このFetch APIにキャッシュの仕組みを追加することで、通信の回数を減らして表示を速くすることができます。
3. メモリ内キャッシュの基本的な仕組み
まずはReactのコンポーネント内に「キャッシュ用の変数」を用意して、APIレスポンスを一時保存する方法を見てみましょう。
import React, { useState } from "react";
const cache = {};
function App() {
const [data, setData] = useState("データ未取得");
const fetchData = async () => {
const url = "https://api.example.com/info";
if (cache[url]) {
setData(cache[url]);
return;
}
const response = await fetch(url);
const result = await response.json();
cache[url] = result.message;
setData(result.message);
};
return (
<div>
<h1>{data}</h1>
<button onClick={fetchData}>データ取得</button>
</div>
);
}
export default App;
4. キャッシュの保存先はどこ?
上記の例では、const cache = {}という形でJavaScriptのオブジェクトに保存しています。これは「メモリ内キャッシュ」と呼ばれます。
ただしこの方法は、ページを再読み込みするとデータが消えてしまいます。より永続的に保存したい場合は「localStorage」や「sessionStorage」を使います。
5. localStorageを使ったキャッシュ方法
localStorageを使えば、ページを再読み込みしてもキャッシュされたデータを保持できます。例としては次のようになります。
const fetchData = async () => {
const cacheKey = "api_cache";
const cached = localStorage.getItem(cacheKey);
if (cached) {
setData(cached);
return;
}
const response = await fetch("https://api.example.com/info");
const result = await response.json();
localStorage.setItem(cacheKey, result.message);
setData(result.message);
};
このように書くことで、一度取得したデータをブラウザに保存して、次回以降は保存済みデータを表示できます。
6. キャッシュに有効期限をつけるには?
キャッシュは便利ですが、ずっと古いままでは意味がありません。そこで、保存時に「タイムスタンプ(保存時間)」を一緒に保存して、一定時間が過ぎたら再取得するという工夫もできます。
const fetchData = async () => {
const cacheKey = "api_cache_v2";
const cacheTimeKey = "api_cache_time";
const cached = localStorage.getItem(cacheKey);
const cacheTime = localStorage.getItem(cacheTimeKey);
const now = Date.now();
const oneHour = 1000 * 60 * 60;
if (cached && cacheTime && now - cacheTime < oneHour) {
setData(cached);
return;
}
const response = await fetch("https://api.example.com/info");
const result = await response.json();
localStorage.setItem(cacheKey, result.message);
localStorage.setItem(cacheTimeKey, now);
setData(result.message);
};
これで「1時間以内はキャッシュを使う、それ以降は再取得する」といった処理が可能になります。
7. キャッシュ処理が便利な場面とは?
- 変更の少ないデータを扱うとき(例:都道府県一覧、カテゴリリスト)
- 通信量を減らしたいとき(モバイル環境など)
- 表示を速くしたいとき(UX向上のため)
Reactでキャッシュを使うことで、サーバーとの通信回数を減らし、アプリのレスポンスを改善することができます。
まとめ
ここまでReactにおけるFetch APIのレスポンスキャッシュについて、基本的なメモリ内キャッシュからlocalStorageを活用した永続化、さらには有効期限の設定方法まで詳しく解説してきました。フロントエンド開発において、APIからのデータ取得は避けては通れない道ですが、ただ取得するだけでなく「いかに効率よく管理するか」が、ユーザー体験(UX)を劇的に向上させる鍵となります。
特にReactのようなシングルページアプリケーション(SPA)では、画面遷移のたびに同じデータをフェッチしてしまうと、ネットワーク帯域の無駄遣いになるだけでなく、ユーザーに「待機時間」というストレスを与えてしまいます。今回ご紹介した手法を取り入れることで、瞬時に画面が表示される心地よいアプリを目指すことができます。
実践的なキャッシュ戦略の振り返り
キャッシュ戦略を選ぶ際は、扱うデータの性質を見極めることが重要です。短期間のセッションで十分な場合はコンポーネント外の変数やステートを利用し、ユーザーがブラウザを閉じても保持したい場合はストレージを利用しましょう。また、最新性が求められるニュースや株価のようなデータには、キャッシュの有効期限(TTL)を短く設定するか、SWR(Stale-While-Revalidate)のような高度なライブラリの検討も視野に入れると良いでしょう。
応用:カスタムフックによるキャッシュの共通化
実際のプロジェクトでは、複数のコンポーネントで同じようなキャッシュ処理を書くのは非効率です。そこで、再利用可能な「カスタムフック」としてロジックを切り出すのがReactらしい書き方です。以下に、汎用的なキャッシュ機能付きデータフェッチフックの例を紹介します。
import { useState, useEffect } from "react";
/**
* キャッシュ機能付きカスタムフック
* @param {string} url 取得先のURL
* @returns {object} { data, loading, error }
*/
const useCachedFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
// localStorageからキャッシュを確認
const cachedData = localStorage.getItem(url);
if (cachedData) {
setData(JSON.parse(cachedData));
setLoading(false);
return;
}
// キャッシュがない場合はフェッチを実行
const response = await fetch(url);
if (!response.ok) {
throw new Error("データの取得に失敗しました");
}
const result = await response.json();
// 取得データをキャッシュに保存
localStorage.setItem(url, JSON.stringify(result));
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
export default useCachedFetch;
このようにロジックをカプセル化することで、コンポーネント側のコードは非常にスッキリします。メンテナンス性も高まり、バグの混入を防ぐことにも繋がります。Reactの基本をマスターした後は、こうした「自分だけの便利な道具」を作っていくのが上達への近道です。
これからのステップ
もし、より大規模なアプリケーションを開発する場合は、自前でキャッシュロジックを書く代わりに「TanStack Query (React Query)」や「SWR」といったライブラリを導入することも検討してみてください。これらのライブラリは、今回学んだ「キャッシュの保存」「有効期限の管理」「データの再取得」といった機能をさらに高度に、かつ簡単に実装できるように設計されています。
しかし、ライブラリが裏側で何をやっているのかを理解するためには、今回のような生のJavaScript/Reactによる実装経験が不可欠です。まずは小さなプロジェクトから、自分の手でキャッシュを制御する楽しさを味わってみてください。パフォーマンスが最適化されたアプリは、開発者にとってもユーザーにとっても最高の成果物になります。
生徒
「先生、ありがとうございました!キャッシュを使うことで、あんなに何度も走っていたAPIリクエストが劇的に減って、アプリの動きがサクサクになりました。特にカスタムフックにする方法は、他のページでも使い回せるので感動しました!」
先生
「それは良かったです!動作が速くなると、作っている自分自身も楽しくなりますよね。実は、ユーザーが気づかないような数ミリ秒の短縮が、アプリの継続率や満足度に大きく影響するんですよ。エンジニアとしての細かなこだわりが、良いサービスを作ります。」
生徒
「なるほど…。でも一つ気になったのですが、もしサーバー側のデータが更新された場合、キャッシュが残っていると古い情報が表示され続けてしまいますよね?そのあたりはどう制御するのが正解なんでしょうか。」
先生
「鋭い指摘ですね!それが『キャッシュの無効化(Invalidation)』という、実はコンピュータサイエンスで最も難しいと言われる課題の一つです。解決策としては、今回紹介した有効期限(TTL)を設ける方法のほかに、データ更新用のボタンを用意して手動でキャッシュをクリアしたり、特定の操作(データの保存や削除)が行われたタイミングでキャッシュを破棄するロジックを組んだりします。」
生徒
「キャッシュの無効化…奥が深いですね。次は、特定の条件でキャッシュを更新する仕組みにも挑戦してみたいです!」
先生
「その意気です!まずは今回学んだlocalStorageの使い方をマスターして、データの型やエラーハンドリングも意識してみてください。TypeScriptを使っているなら、APIレスポンスの型定義をしっかり行うことで、キャッシュから取り出したデータも安全に扱えるようになりますよ。」
生徒
「はい、次はTSXで型安全なキャッシュ機能を作ってみます!コードを書くのがもっと楽しくなってきました。」
先生
「素晴らしいですね。もし詰まったらいつでも聞いてください。Reactの世界は広いですが、一歩ずつ進んでいけば必ず使いこなせるようになりますよ。頑張りましょう!」