前回の記事で TanStack Query に入門して基本的な使い方を知りましたが、この記事では stale
/ cache
の動きについて試行した結果も踏まえて解説です。
動作環境は以下の続きにしています。
ここではキャッシュの動きを理解するために、以下の単位で記載しています。
・フェッチしたデータの鮮度(stale
(古い)or fresh
(新しい))
・自動フェッチのタイミング
・キャッシュの有効期限
フェッチしたデータの鮮度
デフォルトでは即 stale (古い)扱い
useQuery
でクエリすると、キャッシュしたデータは stale (古い)とみなされる。
前回作ったコードで見てみます。
最初、画面表示にはリクエストを投げ、取得をしているのがわかります。
devtool でも stale になっていますね。
その後、別のタブにフォーカスを変えて、再度このタブに戻って来た場合、戻ってきタイミングに合わせてもう一度リクエストが飛んでいます。
画面上では分かりにくいですが、Tanstak Query の Github Star をつけました。そのため、2回目の取得結果が反映されたので、star の部分が 32292 → 32293 に更新されています。ちゃんと反映されています。
staleTime で調整可能
stale になるまでの時間は staleTime
で設定できます(ms)。
staleTime のデフォルト値は 0(冒頭の通り取得した時点で古い)です。そのため、上の例ではタブを切り替えて戻るたびにリクエストが飛びます。
staleTime
を設定します(今回個別の useQuery に指定しています)。5s です。
const { isLoading, isFetching, isError, data, error, refetch } =
useQuery<RepositoriesResponse>({
queryKey: ['orgs', { key }],
queryFn: getReposFn,
staleTime: 1000 * 5,
})
分かりにくいですが、取得直後は devtool で 「fresh」 の状態です。5s 後に「stale」になります。
また、「fresh」の状態でタブを変えて戻ってきてもリクエストは投げられません。
自動再フェッチのタイミング
公式には以下の様に書かれています。
Stale queries are refetched automatically in the background when:
https://tanstack.com/query/v4/docs/react/guides/important-defaults
New instances of the query mount
The window is refocused
The network is reconnected
The query is optionally configured with a refetch interval
上のそれぞれのタイミングは、各種設定で変更が可能(以下)。実際には boolean以外に関数が渡せたりします。詳細は公式を参照ください。
const { isLoading, isFetching, isError, data, error, refetch } =
useQuery<RepositoriesResponse>({
queryKey: ['orgs', { key }],
queryFn: getReposFn,
// refetchOnMount: false,
// refetchOnWindowFocus: false,
// refetchOnReconnect: false,
// refetchInterval: 1000,
// staleTime: 1000 * 5,
})
タイミング | 設定項目 | デフォルト値 | 備考 |
---|---|---|---|
クエリの新しいインスタンスがマウントされたとき | refetchOnMount | true | |
ウィンドウに再度フォーカスがあたったとき | refetchOnWindowFocus | true | |
ネットワークが再接続したとき | refetchOnReconnect | true | |
オプションで設定したリフェッチのインターバル | refetchInterval | false | msで数値指定するとその間隔で再取得 |
キャッシュしたデータの有効期限
上のように一度キャッシュしたデータの有効期限も設定が可能です。
結論としては、設定は以下のように、これまでと同じように cacheTime
という値を ms で指定します。デフォルトは 5分で、非アクティブなクエリに対するキャッシュはガベージコレクトされます。
const { isLoading, isFetching, isError, data, error, refetch } =
useQuery<RepositoriesResponse>({
queryKey: ['orgs', { key }],
queryFn: getReposFn,
// refetchOnMount: false,
// refetchOnWindowFocus: false,
// refetchOnReconnect: false,
// refetchInterval: 1000,
// staleTime: 1000 * 5,
// cacheTime: 1000 * 60 * 5
})
ここからは検証した結果のメモです。
試したこと
- React Router Dom を追加して2つの画面(A、B)を作る
- A画面でこれまでのようにリクエスト実行 & 表示した後に、画面をBに切り替える
- その後、元の画面 A に戻す
本質ではないですが、検証コードについても記載しておきます。
1. react-router-dom のインストール
yarn add react-router-dom
2. 画面遷移の定義を行い、main.ts はこれを指定する。
import { Link, Outlet, Route, Routes } from 'react-router-dom'
import App from './App'
import App2 from './App2'
export default function Root() {
return (
<div>
<h1>Tanstack Query Cache Test</h1>
<Routes>
<Route path="/" element={<Layout />}>
<Route path="app1" element={<App />} />
<Route path="app2" element={<App2 />} />
</Route>
</Routes>
</div>
)
}
function Layout() {
return (
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/app1">App1</Link>
</li>
<li>
<Link to="/app2">App2</Link>
</li>
</ul>
</nav>
<Outlet />
</div>
)
}
3. 状態がわかりやすいように、App.tsx を変更
....
// HTMLに以下を追加
<table border={1} style={{ borderCollapse: 'collapse' }}>
<thead>
<tr>
<th>isInitialLoading</th>
<th>isLoading</th>
<th>isFetching</th>
<th>isError</th>
<th>data</th>
<th>error</th>
</tr>
</thead>
<tbody>
<tr>
<td>{String(isInitialLoading)}</td>
<td>{String(isLoading)}</td>
<td>{String(isFetching)}</td>
<td>{String(isError)}</td>
<td>{data && JSON.stringify(data).slice(0, 60)}...</td>
<td>{String(error)}</td>
</tr>
</tbody>
</table>
/app1 にアクセスすると、以下のように isLoading / isFetching が true
になり、データ取得に行きます。
データ取得後は isLoading
や isFetching
は false
になり、画面に表示されました。
ここまでが準備。ここから何パターンか確認してみます。
stale(古い)& cache 有効
最初の情報取得直後に stale
になります。
その後、別画面に遷移後、再び戻ると、
・cache は有効期間中なので、画面に戻ったときには前のデータが表示されています。
・stale 状態なので再度リクエストが実行されます。取得後はデータに変更があれば更新されます。
以下は画面イメージです。最初 isFetching
だけが true
になります。データは出ていますね。
取得が終わると false
になってます。
実行結果(stale(古い)& cache 有効) – 別画面に遷移後戻ってきてデータ取得が完了した後
stale(古い)& cache 無効
今後は cacheTime
を 3秒などに変更し、画面切り替え前に cache が無効になっているケースです。
最初の画面表示までは同じです。次に、別画面に遷移して戻ってきたときですが、
・cache が無効なので、データがありません。ので一覧には何も表示されていません。
・stale 状態であり、かつデータもないので、isFetching
だえでなく isLoading
も true
になります。
データ取得後の表示はこれまでと同じになります(ので画面イメージは割愛)。
fresh(古くない)& cache 有効
今度は、stale ではない(Tanstack Query の devtool では fresh
)場合です。
データ取得後は、画面は同じですが devtool 見てわかる通り、fresh
になっています。
この状態で画面遷移 & 画面を戻すと以下となります。
・画面上には先程キャッシュしたデータが表示され、リクエストも投げられません。画面的には上と同じです。
うまくキャッシュが効いていますね。
fresh(古くない)& cache 無効
最後のパターンです。ここまで見るともうやるまでもないですが一応。
・この場合は「stale(古い)& cache 無効」の場合と同じになります。取得したデータの staleTime 期間だとしても、そもそも cache の有効期限を過ぎてしまうと、アクティブでないクエリのキャッシュは削除されます。ので、新規取得と同じフローに流れます(と理解しています)。
サマリ
4パターンのサマリは以下になります。
stale | cache | 動き |
---|---|---|
true | 有効 | ・遷移直後はキャッシュが使われ一覧表示 ・サーバへリクエストを投げ、データ取得 & 更新する |
true | 無効 | ・遷移直後はキャッシュがないので一覧は空 ・サーバへリクエストを投げ、データ取得 & 更新する |
false | 有効 | ・遷移直後はキャッシュが使われ一覧表示 ・データは最新なのでサーバへリクエスト投げない |
false | 無効 | ・遷移直後はキャッシュがないので一覧は空 ・データは最新なのでサーバへリクエスト投げない |
まとめ
stale と cache が何を指しているかさえイメージが湧けば当然の結果ではありますが、改めて確認できた気がします。
ここで書いた設定たちはクエリ個別にも共通設定としても設定可能なので、柔軟に使えそうです。
ものによっては、特定タイミングでのみ更新したいと言ったこともあると思うので、そういうときはstaleTime を無限にするとずっと fresh
にでき、更新したい場合はキャッシュを削除すると再取得にいってくれる、など。useQuery など Tanstack の便利なところは享受し、更新頻度とかは自分たちで制御するとかできそうです。