 queryFn 實(shí)戰(zhàn):用 defaultOptions 為 injectQuery 省去逐條聲明)
TanStack Query Angular 默認(rèn) queryFn 實(shí)戰(zhàn)用 defaultOptions 為 injectQuery 省去逐條聲明【免費(fèi)下載鏈接】query Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/qu/query本篇技術(shù)指南基于 TanStack QueryAngular 適配器 tanstack/angular-query-experimental的 default-query-function 指南講解如何通過QueryClient的defaultOptions.queries.queryFn注冊一個(gè)全局默認(rèn)查詢函數(shù)從而讓injectQuery只需傳入queryKey即可完成數(shù)據(jù)獲取。讀完本文你將掌握完整可運(yùn)行的示例寫法并能在源碼層面理解默認(rèn)值是如何與每次調(diào)用的 options 合并、覆蓋的。核心思路把「怎么取數(shù)據(jù)」聲明一次把「取什么」留給組件在典型的 REST 應(yīng)用里絕大多數(shù)查詢的差異只在于 URL 路徑即queryKey而請求方式axios/fetch 實(shí)例、認(rèn)證頭、錯(cuò)誤歸一化、數(shù)據(jù)解包是高度一致的。如果每個(gè)組件都寫一遍queryFn: () axios.get(...).then(r r.data)會(huì)產(chǎn)生大量重復(fù)代碼且請求層難以統(tǒng)一替換比如全局換 baseURL、統(tǒng)一注入 token。TanStack Query 的解法就是「默認(rèn)查詢函數(shù)」Default Query Function在創(chuàng)建QueryClient時(shí)通過defaultOptions.queries.queryFn提供一個(gè)QueryFunction之后所有查詢在自身未顯式聲明queryFn時(shí)都會(huì)回落到這個(gè)默認(rèn)實(shí)現(xiàn)并且queryFn能收到完整的QueryFunctionContext含queryKey因此可以根據(jù) key 動(dòng)態(tài)拼接請求路徑。完整示例從定義到組件使用下面完整繼承官方指南中的示例代碼覆蓋「定義默認(rèn)函數(shù) → 注冊到客戶端 → 接入 Angular 應(yīng)用 → 組件中只傳 key」四個(gè)環(huán)節(jié)。定義并注冊 defaultQueryFn// Define a default query function that will receive the query key const defaultQueryFn: QueryFunction async ({ queryKey }) { const { data } await axios.get( https://jsonplaceholder.typicode.com${queryKey[0]}, ) return data } // provide the default query function to your app with defaultOptions const queryClient new QueryClient({ defaultOptions: { queries: { queryFn: defaultQueryFn, }, }, })要點(diǎn)說明默認(rèn)函數(shù)簽名是QueryFunction接收一個(gè)上下文對象其中最常用的是queryKey。示例中把queryKey[0]直接當(dāng)作 API 路徑如/posts、/posts/1拼接到 jsonplaceholder 的 baseURL 上這是一種「key 即路由」的約定defaultOptions.queries是全局查詢默認(rèn)值的掛載點(diǎn)除了queryFn還可以放staleTime、retry等本文聚焦queryFnQueryClient構(gòu)造器會(huì)把傳入的defaultOptions保存下來config.defaultOptions || {}后續(xù)所有defaultQueryOptions(...)調(diào)用都從這里讀取見 QueryClient 構(gòu)造函數(shù)。把客戶端接入 Angular 應(yīng)用bootstrapApplication(MyAppComponent, { providers: [provideTanStackQuery(queryClient)], })provideTanStackQuery內(nèi)部通過provideQueryClient把QueryClient注冊為可注入的單例并在 injector 銷毀時(shí)調(diào)用client.unmount()、創(chuàng)建時(shí)調(diào)用client.mount()源碼見 providers.ts。它也支持傳入InjectionToken以支持懶加載路由單獨(dú)掛載 Query并可通過withDevtools()啟用開發(fā)者工具文檔見 providers.ts 的 JSDoc。組件中只傳 queryKeyexport class PostsComponent { // All you have to do now is pass a key! postsQuery injectQueryArrayPost(() ({ queryKey: [/posts], })) // ... } export class PostComponent { // You can even leave out the queryFn and just go straight into options postQuery injectQueryPost(() ({ enabled: this.postIdSignal() 0, queryKey: [/posts/${this.postIdSignal()}], })) // ... }兩個(gè)細(xì)節(jié)值得注意injectQuery接收一個(gè) options 函數(shù)() options而非字面量對象。從 createBaseQuery.ts 的注釋可以確認(rèn)這是刻意設(shè)計(jì)options 被包在函數(shù)里后內(nèi)部嵌入的 signal 表達(dá)式如this.postIdSignal()才能被 Angular 的響應(yīng)式系統(tǒng)追蹤signal 變化時(shí)默認(rèn)選項(xiàng)和 observer 才會(huì)自動(dòng)重算。queryFn被整體省略后類型系統(tǒng)依然完整。injectQuery的泛型參數(shù)ArrayPost/Post直接決定了返回值信號的類型無需本地聲明返回Promise的函數(shù)query-options.ts中的UnusedSkipTokenOptions與DefinedInitialDataOptions等類型正是把queryFn聲明為可選字段的依據(jù)見 query-options.ts。源碼層面默認(rèn) queryFn 是如何生效的第一步createBaseQuery 用 computed 應(yīng)用全局默認(rèn)值injectQuery與injectInfiniteQuery共享同一個(gè)底層工廠createBaseQuery。其中defaultedOptionsSignal是一個(gè)computed每次求值都會(huì)調(diào)用queryClient.defaultQueryOptions(optionsFn())把組件傳入的 options 與全局默認(rèn)值合并const defaultedOptionsSignal computed(() { const defaultedOptions queryClient.defaultQueryOptions(optionsFn()) defaultedOptions._optimisticResults isRestoring() ? isRestoring : optimistic return defaultedOptions })見 create-base-query.ts。由于它是computed當(dāng)optionsFn()內(nèi)引用的 signal例如postIdSignal變化時(shí)合并結(jié)果會(huì)重新計(jì)算enabled、queryKey等也隨之更新。第二步QueryClient.defaultQueryOptions 的合并順序合并邏輯在 QueryClient.defaultQueryOptions 中核心是三段展開const defaultedOptions { ...this.#defaultOptions.queries, // ① 全局默認(rèn)值含我們的 defaultQueryFn ...this.getQueryDefaults(options.queryKey), // ② 按 key 前綴匹配的 setQueryDefaults ...options, // ③ 本次調(diào)用顯式傳入的 options _defaulted: true, }從源碼結(jié)構(gòu)看優(yōu)先級由低到高是defaultOptions.queries構(gòu)造QueryClient時(shí)傳入全局生效setQueryDefaults(queryKey, options)注冊的按 key 前綴匹配的默認(rèn)值getQueryDefaults通過partialMatchKey做前綴匹配見 queryClient.ts每次injectQuery傳入的 options可以覆蓋默認(rèn)queryFn或任何其它字段。這意味著全局注冊defaultQueryFn之后某個(gè)特殊查詢?nèi)钥稍?options 里寫自己的queryFn覆蓋它也可以只對/posts/*這一族 key 用setQueryDefaults提供局部默認(rèn)值而不影響其它 key。方法還會(huì)補(bǔ)全若干派生默認(rèn)值如refetchOnReconnect、throwOnError、networkMode并對skipToken做enabled false的轉(zhuǎn)換見 queryClient.ts。帶_defaulted標(biāo)記的對象會(huì)被直接短路返回避免重復(fù)合并。第三步Query 執(zhí)行時(shí)如何找到 queryFn真正發(fā)起請求前Query會(huì)在this.options.queryFn缺失時(shí)從已訂閱的 observer 中找出攜帶queryFn的那個(gè)作為兜底defaultOptions.queries.queryFn正是在合并階段被放進(jìn)了 observer optionsif (!this.options.queryFn) { const observer this.observers.find((x) x.options.queryFn)見 query.ts。隨后通過ensureQueryFn(this.options, fetchOptions)解析出最終函數(shù)并以其構(gòu)造QueryFunctionContext執(zhí)行query.ts。這條鏈路解釋了為什么「組件里不寫 queryFn」仍然能發(fā)起請求默認(rèn)值在 observer 合并期就已經(jīng)掛到 options 上執(zhí)行期只是讀取。默認(rèn)值、queryOptions 與類型安全如果你偏好把查詢配置抽到組件外部復(fù)用可以用同包的queryOptions幫助函數(shù)query-options.ts。它本身只是恒等函數(shù)價(jià)值在于三個(gè)重載為queryKey打上queryFn的數(shù)據(jù)類型標(biāo)簽const { queryKey } queryOptions({ queryKey: [key], queryFn: () Promise.resolve(5), // ^? Promisenumber })打上標(biāo)簽后queryClient.getQueryData(queryKey)的返回值類型就是number | undefined而不是unknown。把queryOptions與全局defaultQueryFn結(jié)合時(shí)抽出的配置可以只含queryKey數(shù)據(jù)獲取細(xì)節(jié)全部交給默認(rèn)函數(shù)同時(shí)保持端到端的類型推斷。實(shí)踐建議與邊界結(jié)合倉庫實(shí)現(xiàn)給出幾條落地時(shí)的注意點(diǎn)讓 key 成為「請求路徑」的前提是請求層可控。示例中queryKey[0]直接拼 URL適合 REST 且路徑結(jié)構(gòu)穩(wěn)定的 API對 GraphQL 或非路徑型 key默認(rèn)函數(shù)應(yīng)按 key 結(jié)構(gòu)分派從queryKey首元素判斷資源類型再選擇對應(yīng)請求邏輯。默認(rèn)函數(shù)里做集中式錯(cuò)誤/響應(yīng)歸一化。既然queryFn是全局單點(diǎn)axios 攔截器式的解包return data、錯(cuò)誤映射都適合放在這里各組件無需感知。局部覆蓋優(yōu)于復(fù)制粘貼個(gè)別查詢的取數(shù)邏輯確實(shí)不同時(shí)在該次injectQuery的 options 中顯式寫queryFn即可合并順序的第 ③ 層不要為此拆出第二個(gè)QueryClientprovideQueryClient的文檔注釋也說明它更適用于應(yīng)用局部或單元測試場景見 providers.ts。響應(yīng)式依賴依賴 options 函數(shù)。默認(rèn)queryFn的注冊是「應(yīng)用級靜態(tài)配置」而enabled、queryKey這類與 signal 相關(guān)的字段必須在 options 函數(shù)內(nèi)引用 signal如示例的this.postIdSignal() 0才會(huì)隨信號更新——這是 createBaseQuery.ts 注釋中強(qiáng)調(diào)的行為。適用前提defaultOptions.queries.queryFn由 query-core 提供Angular 側(cè)經(jīng)由tanstack/angular-query-experimental的provideTanStackQuery注入客戶端后自動(dòng)生效版本行為以本倉庫當(dāng)前 query-core 源碼 與 Angular 適配器源碼 為準(zhǔn)。小結(jié)默認(rèn) queryFn 模式把「如何請求」從 N 個(gè)組件收斂到 1 處QueryClient配置defaultQueryFn接收queryKey動(dòng)態(tài)構(gòu)造請求provideTanStackQuery(queryClient)完成應(yīng)用級注入injectQuery(() ({ queryKey: [...] }))則讓組件代碼退化為「聲明 key 泛型類型」。源碼鏈路上computed包裹的defaultQueryOptions合并全局默認(rèn) → 按 key 前綴默認(rèn) → 調(diào)用方 options與Query執(zhí)行期的queryFn兜底查找共同保證了這套省略寫法在響應(yīng)式更新下的正確性。倉庫中的 Angular 示例應(yīng)用bootstrapApplicationappConfig提供provideTanStackQuery可直接作為搭建起點(diǎn)。【免費(fèi)下載鏈接】query Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/qu/query創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考