TanStack Queryによるフロントエンドデータの自動同期:更新の仕組みを理解する
Takashi Yamamoto
Infrastructure Engineer · Leapcell

はじめに
ウェブ開発のダイナミックな世界において、ユーザーに表示されるデータが常に最新かつバックエンドサーバーと一貫していることを保証することは、継続的な課題です。従来のアプローチでは、手動でのデータ取得、複雑な状態管理、または頻繁で潜在的に非効率的なポーリングメカニズムがしばしば用いられてきました。これらの方法では、UIの陳腐化、サーバー負荷の増加、そして理想的とは言えないユーザーエクスペリエンスにつながる可能性があります。ここで、TanStack Query(旧React Query)のようなモダンなデータ取得ライブラリが輝きを放ちます。開発者のエクスペリエンスとパフォーマンスを念頭に設計されたTanStack Queryは、サーバー状態を管理するための強力な機能を提供しており、特にバックグラウンドでのデータの自動同期とリフレッシュに関するインテリgentな機能が注目に値します。この自動同期がどのように実現されているかを理解することは、堅牢で応答性が高く、効率的なフロントエンドアプリケーションを構築する上で不可欠です。
TanStack Queryのコアコンセプト
自動同期の詳細に入る前に、TanStack Query内のいくつかの重要な概念について基本的な理解を確立しましょう。
- クエリ(Query): データの非同期リクエスト(例:
useQuery)を表します。これは「クエリキー」を使用してデータを一意に識別します。 - クエリキー(Query Key): キャッシュされた各データの一意の識別子(配列または文字列)です。これにより、TanStack Queryはどのデータが何であるかを認識します。
- キャッシュ(Cache): TanStack Queryは、解決されたクエリのインメモリキャッシュを維持します。特定のクエリキーでデータをリクエストすると、TanStack Queryはまずキャッシュをチェックします。データが利用可能で、陳腐化していない場合、即座に返されます。
- Stale Time(新鮮でなくなるまでの時間): キャッシュされたデータが「Stale(古くなった)」と見なされるまでの期間です。データがStaleになると、TanStack Queryは、そのデータに次にアクセスまたは観測されたときに、バックグラウンドで再取得を試みます。
- Cache Time(キャッシュ保持期間): 非アクティブなキャッシュドクエリデータがガベージコレクションされるまでの期間です。クエリコンポーネントがアンマウントされ、データがもはや観測されなくなった場合、
cacheTimeが期限切れになるまでキャッシュに残ります。 - Refetching(再取得): バックエンドから最新のデータを取得するために、クエリ関数を再実行するプロセスです。これは自動的に発生することも、手動でトリガーすることもできます。
- Background Refetching(バックグラウンド再取得): TanStack Queryが、初期レンダリング時にローディングスピナーを表示することなくデータを再取得する際に、UIの応答性を高めます。ユーザーは、新しいデータが到着するまで、短い間だけ古いデータを見ることになります。
自動同期の原則
TanStack Queryの自動同期は、「Stale-while-revalidate(古くなるまで表示、再検証)」の原則に基づいています。これは、クエリのデータがStaleと見なされた場合、TanStack Queryは次のようすることを意味します。
- キャッシュされた(Staleな)データをUIに即座に返します。
- バックグラウンドで、サーバーから最新のデータを取得するための再取得操作を開始します。
- 再取得が完了すると、新しいデータでキャッシュを更新し、新しい情報でUIを再レンダリングします。
このエレガントなメカニズムは、ローディング状態によるUIのちらつきを防ぎ、データが一時的に古い可能性がある場合でも、ユーザーに即時応答を提供します。
自動データリフレッシュのメカニズム
TanStack Queryは、明示的なポーリングロジックなしでデータを自動的に再取得および同期するための、いくつかのインテリgentなメカニズムを採用しています。
1. ウィンドウフォーカス時の再取得(Refetch on Window Focus)
これはおそらく最も強力で、しばしば見過ごされがちな機能です。ユーザーがアプリケーションから離れて(タブを切り替えたり、ウィンドウを最小化したりするなど)から戻ってきたときに、TanStack QueryはアクティブでStaleなすべてのクエリを自動的に再取得します。これにより、ユーザーがアプリに再び関与したときに、最新のデータが表示されるようになり、ユーザーが離れている間に発生した可能性のあるバックエンドの更新とのギャップを埋めます。
例:
import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; interface Todo { id: number; title: string; completed: boolean; } const fetchTodos = async (): Promise<Todo[]> => { const { data } = await axios.get<Todo[]>('/api/todos'); return data; }; function TodoList() { const { data, isLoading, error } = useQuery<Todo[], Error>({ queryKey: ['todos'], queryFn: fetchTodos, staleTime: 5 * 60 * 1000, // 5分後にデータはStaleと見なされる // デフォルトでは refetchOnWindowFocus は true です }); if (isLoading) return <div>Loading todos...</div>; if (error) return <div>An error occurred: {error.message}</div>; return ( <ul> {data?.map(todo => ( <li key={todo.id}>{todo.title} - {todo.completed ? 'Done' : 'Pending'}</li> ))} </ul> ); } export default TodoList;
この例では、TodoListがマウントされているタブからユーザーが5分以上離れて(staleTime)、その後戻ってきた場合、TanStack Queryはバックグラウンドで自動的にtodosを再取得します。
2. マウント時の再取得(Refetch on Mount)
useQueryフックのインスタンスがマウントされると、そのデータがStaleであれば、TanStack Queryは自動的にバックグラウンドでの再取得を開始します。これは、頻繁にマウントおよびアンマウントされるコンポーネントに便利で、常に最新のデータを表示することを保証します。
3. 再接続時の再取得(Refetch on Reconnect)
ユーザーのネットワーク接続が切断され、その後回復した場合、TanStack Queryはこれをインテリgentに検出し、アクティブでStaleなすべてのクエリを自動的に再取得します。この機能は、間欠的な接続環境でのユーザーエクスペリエンスを大幅に向上させます。なぜなら、再接続時にアプリケーションは自動的に「自己修復」されるからです。
4. インターバルでの再取得(Refetch on Interval)(ポーリング)
TanStack Queryは手動ポーリングの必要性を減らすことを目指していますが、他のメカニズムが十分でない場合に、ほぼリアルタイムの更新が重要なユースケースのために、指定された間隔でデータを再取得するオプションを提供しています。
例:
import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; interface StockPrice { symbol: string; price: number; timestamp: string; } const fetchStockPrice = async (symbol: string): Promise<StockPrice> => { const { data } = await axios.get<StockPrice>(`/api/stock/${symbol}`); return data; }; function StockPriceDisplay({ symbol }: { symbol: string }) { const { data, isLoading, error } = useQuery<StockPrice, Error>({ queryKey: ['stockPrice', symbol], queryFn: () => fetchStockPrice(symbol), refetchInterval: 5000, // 5秒ごとの再取得 staleTime: Infinity, // Intervalによるものでない限り、データはそれ自体でStaleになることはない }); if (isLoading) return <div>Loading stock price...</div>; if (error) return <div>An error occurred: {error.message}</div>; return ( <div> <h3>{data?.symbol} Price: ${data?.price.toFixed(2)}</h3> <p>Last updated: {new Date(data?.timestamp || '').toLocaleTimeString()}</p> </div> ); } export default StockPriceDisplay;
ここでは、指定されたsymbolのstockPriceが5秒ごとに再取得され、ほぼリアルタイムの更新が提供されます。staleTime: Infinityが使用されていることに注意してください。なぜなら、ここではrefetchIntervalが鮮活性の主なメカニズムだからです。
5. 手動での無効化とミューテーション(Manual Invalidations and Mutations)
上記のメカニズムが自動的なバックグラウンド同期を処理する一方で、サーバー上の特定のデータが変更されたため再取得が必要であることをTanStack Queryに明示的に指示する必要がある場合もあります。これは、リソースの作成、更新、または削除などのアクションを実行した後、特に重要です。
queryClient.invalidateQueries(queryKey): これは、queryKeyに一致するクエリをStaleとしてマークするための強力なメソッドです。無効化されると、次にそれらのクエリが観測/アクセスされたときに、バックグラウンドで再取得がトリガーされます。
ミューテーションとの例:
import { useMutation, useQueryClient } from '@tanstack/react-query'; import axios from 'axios'; interface NewTodo { title: string; } interface CreatedTodo extends NewTodo { id: number; } const createTodo = async (newTodo: NewTodo): Promise<CreatedTodo> => { const { data } = await axios.post<CreatedTodo>('/api/todos', newTodo); return data; }; function AddTodoForm() { const queryClient = useQueryClient(); const mutation = useMutation<CreatedTodo, Error, NewTodo>({ mutationFn: createTodo, onSuccess: () => { // 成功した作成後に 'todos' クエリを無効化します // これにより、TodoList コンポーネントはデータを再取得するようになります queryClient.invalidateQueries({ queryKey: ['todos'] }); }, }); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); const formData = new FormData(event.currentTarget as HTMLFormElement); const title = formData.get('title') as string; mutation.mutate({ title }); }; return ( <form onSubmit={handleSubmit}> <input name="title" placeholder="New todo title" /> <button type="submit" disabled={mutation.isPending}> {mutation.isPending ? 'Adding...' : 'Add Todo'} </button> {mutation.isError && <div>Error adding todo: {mutation.error?.message}</div>} </form> ); } export default AddTodoForm;
このシナリオでは、AddTodoFormを通じて新しいtodoが正常に作成された後、queryClient.invalidateQueries({ queryKey: ['todos'] })が呼び出されます。これにより、TanStack Queryは['todos']キーに関連付けられたデータ(TodoListが使用しているもの)がStaleになったことを認識します。その後、TodoListはページリフレッシュや手動の状態更新を必要とせずに、新しく追加されたtodoを表示するために自動的にバックグラウンドでの再取得をトリガーします。
結論
TanStack Queryは、フロントエンドアプリケーションにおけるサーバー状態の管理方法を根本的に変え、手動でのデータ同期から、インテリgentで宣言的なアプローチへと移行させます。ウィンドウフォーカス時、マウント時、再接続時の再取得、および柔軟な無効化戦略のようなメカニズムを活用することで、アプリケーションのUIがバックエンドデータと一貫して同期されていることを保証します。これにより、応答性の高いインターフェース、コード量の削減、そして最終的にはよりパフォーマンスが高く保守性の高いアプリケーションという、大幅に改善されたユーザーエクスペリエンスが実現します。TanStack Queryの自動同期機能を活用することは、真にモダンでダイナミックなウェブエクスペリエンスを構築するための重要なステップです。

