TanStack Queryのrefetchとenabledについて誤解してた話
これは、日本CTO協会24卒 Advent Calendar 2024 の21日目の記事です!(遅れましたmm)
疑問に思ったコード
下記のコードを読んでいて、
「enabled: false
のクエリをrefetch()
しているのに、なんでクエリが実行できるんだろう🤔」
そう思いました。
enabledがクエリの実行を制御するものだと理解していたため、falseを指定したクエリをrefetchしたら、実行できないと思っていたからです。
const query = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
enabled: false
});
// どこかで実行
query.refetch(); // これが動く!?
分かったこと
1. refetch()はenabledを無視する
refetchの実装を見ると
// packages/query-core/src/queryObserver.ts
refetch({ ...options }: RefetchOptions = {}): Promise<
QueryObserverResult<TData, TError>
> {
return this.fetch({
...options,
})
}
enabledのチェックがないため、そのままfetch
を実行していました。
2. refetchQueriesはenabledを尊重する
一方、refetchQueriesの実装では
// packages/query-core/src/queryClient.ts
refetchQueries(...) {
const promises = notifyManager.batch(() =>
this.#queryCache
.findAll(filters)
.filter((query) => !query.isDisabled()) // ここで除外!
.map((query) => query.fetch())
)
}
isDisabled()
でフィルタリングされていました。
3. なぜ違うのか?
isDisabledの実装を見ると
// packages/query-core/src/query.ts
isDisabled(): boolean {
if (this.getObserversCount() > 0) {
return !this.isActive()
}
return (
this.options.queryFn === skipToken ||
this.state.dataUpdateCount + this.state.errorUpdateCount === 0
)
}
isActive(): boolean {
return this.observers.some(
(observer) => resolveEnabled(observer.options.enabled, this) !== false
)
}
enabledを見て、除外していたことがわかりました。
3. v3からv4での変更
調べてみると、v3まではrefetchQueries
は無効化されたクエリも実行していたようです。
v3までの動作
v3まではenabledの状態に関係なく、以下のメソッドで全てのクエリが再フェッチされていました。
// v3: enabled: falseでもリフェッチされる
queryClient.refetchQueries()
queryClient.invalidateQueries()
これにより、意図しないデータフェッチが発生する可能性がありました。
「あるクエリを意図的にenabledをfalseにしているのに、システム全体のリフェッチで予期せずフェッチされてしまう」なんてことが起きていました。
v4での改善
v4ではrefetchQueries
の実装が変更され、無効化されたクエリは明示的にスキップされるようになりました。
// v4の実装
const promises = notifyManager.batch(() =>
this.#queryCache
.findAll(filters)
.filter((query) => !query.isDisabled()) // この行が追加された
.map((query) => query.fetch())
)
この変更により、refetchQueries
は無効化されたクエリをスキップするようになり、意図しないフェッチの防止し、パフォーマンスの改善(不要なフェッチの削減)を実現することができました。
4. enabledについて
TanStack Queryのメンテナーは以下のように説明していました
Strictly speaking, a query itself cannot be enabled or disabled - it can just "be". Observers can be disabled if they shouldn't trigger requests by themselves, that's all.
enabled is an observer level property, which means you can call useQuery with the same key twice in two components, both being differently enabled.
訳:
- 厳密に言えば、クエリ自体に有効/無効という状態はありません - それは単に"存在する"だけです。Observerは、自身でリクエストをトリガーすべきでない場合に無効化されるだけです。
- enabledはobserverレベルのプロパティです。つまり、同じキーに対して2つのコンポーネントから異なるenabledを持つuseQueryを呼び出すことができます。
この説明から分かることは
- enabledはクエリではなく、observerの性質
- 個別の
refetch()
は意図的な手動実行 -
refetchQueries
は自動的な一括処理 - enabledはあくまでobserverの自動フェッチを制御するためのもの
まとめ
TanStack Queryのrefetchとenabledについて、下記のように整理することができました。
refetchやrefetchQueries, enabledをしっかり使いこなせるようにしていきたいです!
- クエリ自体に「有効/無効」の状態はなく、
enabled
はオブザーバーの挙動を制御するのみ -
refetch()
はenabled
の設定に関わらず、手動でのデータ再取得を可能にする
Discussion