Vue の reactivity を使った tanstack/vue-query v5 の制御方法検討のために、実装・文書・PRを漁る
@tanstack/vue-query
v4 を使って WebAPI を呼び出す hook を実装していたとき、 WebAPI 用のパラメーター(paramRef
)の状態 (string or undefined) に応じて enabled
オプションで API の呼び出しを制御していた。
切り替えたいオプションは enabled
だけだけれど、queryKey
と queryFn
も paramRef
に依存している (undefined のときは実行させたくないし、型も不正)。
そのため、useQuery のオプションを丸ごと computed していた。
export const useApi = (paramRef: Ref<string | undefined>) => {
return useQuery(
computed(() => {
const param = paramRef.value;
return !param
? { enabled: false }
: {
queryKey: createQueryKey(param),
queryFn: () => callApi(param),
};
}),
);
};
@tanstack/vue-query
v5 に更新したところ、型エラーが出るようになってしまった。解決するためには、createQueryKey(param)
と同じ、またはサブセットの型を定義する必要があった。
export const useApi = (paramRef: Ref<string | undefined>) => {
return useQuery(
computed(() => {
const param = paramRef.value;
return !param
? {
enabled: false,
queryKey: ["key", "__DUMMY__"] as const, // 👈 ???!!!!
}
: {
queryKey: createQueryKey(param), // これの型が readonly ["key", string]
queryFn: () => callApi(param),
};
}),
);
};
まぁ、嫌だ…。 使い方が間違っているはずだし、ドキュメント等を読んでみる。
https://tanstack.com/query/latest/docs/framework/vue/reference/useQuery
enabled: boolean | (query: Query) => boolean
- Set this to false to disable this query from automatically running.
- Can be used for Dependent Queries.
enabled
に関数を渡すことで、 callback で query
を受け取って true/false を決められそう、って思ったけれど、setup 時に query が undefined になる。
query が型に反して undefined になるのは、vue-query が query インスタンスを引数で渡していないから。
https://github.com/TanStack/query/blob/v5.55.4/packages/vue-query/src/useBaseQuery.ts#L90
Issue も出来ていた。
vue-query の lifecycle 的に setup 時に query instance を取得するのは厳しそう。型の問題もあるらしい (as any
使ってるぐらいだしな…)
[vue-query] Callback for enabled does not provide query as parameter #7905
ただし、enabled
オプションに関数を渡すのは正しいっぽい。'should be enabled to accept getter function'
っていうテストコードもある。
ドキュメントの別の記述だと、enabled
には computedRef を渡している。
https://tanstack.com/query/v5/docs/framework/vue/guides/disabling-queries#lazy-queries
とりあえず分かったのは、vue-query の api reference は当てにならないっぽいこと…。Vue と React だと API も 内部の実装も結構違うのに API Document が共通になっている。
https://github.com/TanStack/query/blob/main/docs/framework/vue/reference/useQuery.md
enabled
オプションの実装タイミングを掘ってみると、結構ややこしい状況になっている。
Add possibility to pass a callback to enabled. #7566 での query-core に対する修正で enabled
オプションが関数を受け付けるようになった。
これは v5.48.0 でリリースされている
一方、 vue-query は v5.47.0 の時点で既に enabled に関数を渡せるようになっていた。
feat(vue-query): let composables accepts enabled as a getter function #6018 で computed だけでなく getter が受け取れるように修正されている。
// Before useQuery({ queryKey: ["TODO"], queryFn: () => Promise.resolve({ data: [] }), enabled: computed(() => isMounted.value && isAuth.value), }); // After useQuery({ queryKey: ["TODO"], queryFn: () => Promise.resolve({ data: [] }), enabled: () => isMounted.value && isAuth.value, });
つまり、enabled
オプションは package 毎にまったく別のものが提供されている…。
- query-core (または react-query) における query を引数にした callback
- vue-query における Vue getter
Vue getter は一般的な getter/setter とは異なる概念みたいだ。これは別の機会に調べよう (https://blog.vuejs.org/posts/vue-3-3#better-getter-support-with-toref-and-tovalue)
結局どうすんの
paramRef
に依存している queryKey 問題
queryKey
オプションは MaybeRef
型なので、Ref<string | undefined>
のまま渡しちゃう
paramRef
に依存している queryFn 問題
skipToken が便利そう
export const useApi = (paramRef: Ref<string | undefined>) => {
return useQuery({
queryKey: createQueryKey(paramRef),
queryFn: computed(() => {
const param = paramRef.value;
if (!param) {
return skipToken; // 👈 `enabled` オプションの代わりに `skipToken` を返して disable にする
}
return () => callApi(param); // 👈 param が string なら API を実行する
}),
});
};