TanStack Query Preact 快速上手:Queries、Mutations 与 Query Invalidation 三大核心概念实战
【免费下载链接】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.项目地址: https://gitcode.com/GitHub_Trending/qu/query
Preact 作为轻量级 React 替代方案,同样需要一套健壮的异步状态管理工具来处理服务端数据获取、缓存与同步。本指南基于@tanstack/preact-query(本仓库 packages/preact-query 中的核心包)讲解其快速上手路径:从安装、创建QueryClient并注入 Provider,到掌握查询(Queries)、变更(Mutations)与查询失效(Query Invalidation)这三大核心概念。读完本指南,你将能够在 Preact 应用中完成「拉取数据 → 渲染 loading/error/success 三态 → 提交变更 → 自动失效并刷新数据」的完整闭环,并理解底层缓存与订阅机制。
本文内容的主体来自 docs/framework/preact/quick-start.md(该文档由生成流程从 docs/framework/react/quick-start.md 通过替换包名与框架名转写而来,参见 scripts/generate-docs.ts 中的文档生成机制),并辅以仓库源码与真实示例进行纵深验证。
安装与准备
通过包管理器安装
使用你习惯的包管理器安装@tanstack/preact-query:
npm i @tanstack/preact-query或使用 pnpm / yarn / bun / deno:
pnpm add @tanstack/preact-queryyarn add @tanstack/preact-querybun add @tanstack/preact-querydeno add @tanstack/preact-query根据 packages/preact-query/package.json 中的依赖声明,@tanstack/preact-query的核心依赖是@tanstack/query-core(workspace 内同版本维护),而preact以^10.0.0作为 peerDependency 声明,即 Preact 10 及以上版本均可使用;当前仓库示例使用preact ^10.28.0(见 examples/preact/simple/package.json)。
通过 CDN 引入
如果不想使用模块打包器或包管理器,可以直接通过 ESM 兼容 CDN(如 ESM.sh)在 HTML 中以<script type="module">方式引入,参考 docs/framework/preact/installation.md:
<script type="module"> import { render } from 'https://esm.sh/preact@10.23.1' import { QueryClient } from 'https://esm.sh/@tanstack/preact-query' </script>环境要求
@tanstack/preact-query面向现代浏览器优化。官方 React 版本的环境要求(Chrome >= 91、Firefox >= 90、Edge >= 91、Safari >= 15、iOS >= 15、Opera >= 77,见 docs/framework/react/installation.md)同样适用于 Preact 版本;如果目标环境较旧,可能需要补充 polyfill,并对node_modules中的库自行转译。仓库中还建议搭配@tanstack/eslint-plugin-query(文档见 docs/eslint/eslint-plugin-query.md)在编码阶段提前发现错误与不一致用法。
创建 QueryClient 并通过 Provider 注入
TanStack Query 的所有缓存与订阅能力都围绕一个QueryClient实例展开。第一步是创建它,并通过QueryClientProvider注入到组件树顶层:
import { QueryClient, QueryClientProvider, } from '@tanstack/preact-query' // Create a client const queryClient = new QueryClient() function App() { return ( // Provide the client to your App <QueryClientProvider client={queryClient}> <Todos /> </QueryClientProvider> ) }从源码角度看,QueryClientProvider的实现(packages/preact-query/src/QueryClientProvider.tsx)做了两件事:
- 通过 Preact Context 下发 client:
QueryClientProvider内部使用createContext创建QueryClientContext,并用<QueryClientContext.Provider value={client}>包裹 children,使整个子树都能通过useQueryClient读取到该实例; - 绑定客户端生命周期:组件挂载时调用
client.mount()、卸载时调用client.unmount()。mount()会让 client 订阅 focus/online 事件——当应用重新获得焦点或恢复联网时,自动恢复被暂停的变更并触发必要的重新获取。
useQueryClient从最近的 Context 中读取 client;如果组件树中没有QueryClientProvider,它会抛出'No QueryClient set, use QueryClientProvider to set one'的错误(见 packages/preact-query/src/QueryClientProvider.tsx),因此请务必保证 Provider 包裹在所有使用 hook 的组件之上。
值得一提的还有包的导出结构:@tanstack/preact-query在 packages/preact-query/src/index.ts 中直接export * from '@tanstack/query-core',因此QueryClient、QueryCache、MutationCache等核心类都从同一个包导入即可,无需额外安装依赖。
三大核心概念总览
@tanstack/preact-query的核心功能由以下三个概念构成(它们也是 docs/framework/preact/quick-start.md 一文的骨架):
- Queries(查询):声明式地依赖某个异步数据源,用唯一 key 缓存、共享数据;
- Mutations(变更):用于创建/更新/删除数据或执行服务端副作用;
- Query Invalidation(查询失效):当数据确知过期时,智能地标记查询失效并触发后台重新获取。
下面依次深入这三个概念。
概念一:查询(Queries)
查询的本质与最小用法
一个查询是对某个异步数据源的声明式依赖,它与一个唯一 key绑定。任何基于 Promise 的方法(包括 GET 和 POST)都可以用于从服务端获取数据;但请注意:如果某个方法会修改服务端数据,官方推荐改用 Mutations 而不是 Query。
要订阅一个查询,在组件或自定义 hook 中调用useQuery,至少传入两个参数:
- 查询的唯一 key(
queryKey); - 返回 Promise 的函数(
queryFn),该 Promise 要么 resolve 出数据,要么 reject 抛出错误。
import { useQuery } from '@tanstack/preact-query' function App() { const info = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList }) }这个唯一 key在内部被用于重新获取、缓存以及在应用各处共享查询结果。key 相同(或前缀匹配)的多个useQuery调用会共享同一份缓存数据。
在源码层面,useQuery的所有重载最终都收敛到同一个实现:useBaseQuery(options, QueryObserver, queryClient)(见 packages/preact-query/src/useQuery.ts),QueryObserver来自@tanstack/query-core。也就是说,Preact 版本与 React 版本共用同一套核心观察者机制,只是适配层(订阅方式、context 提供)不同。
查询状态(status)
useQuery返回的result对象包含模板渲染所需的全部信息。任意时刻,一个查询只能处于以下三种状态之一:
| 状态 | 说明 |
|---|---|
isPending或status === 'pending' | 查询还没有数据 |
isError或status === 'error' | 查询遇到了错误 |
isSuccess或status === 'success' | 查询成功且数据可用 |
除此之外,根据查询所处的状态还有更多可用信息:
error:查询处于isError状态时,可通过error属性拿到错误对象;data:查询处于isSuccess状态时,可通过data属性拿到数据;isFetching:任意状态下,只要查询正在获取数据(包括后台重新获取),isFetching即为true。
对于大多数查询,标准的渲染模式是:先检查isPending,再检查isError,最后默认数据可用并渲染成功态:
function Todos() { const { isPending, isError, data, error } = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList, }) if (isPending) { return <span>Loading...</span> } if (isError) { return <span>Error: {error.message}</span> } // We can assume by this point that `isSuccess === true` return ( <ul> {data.map((todo) => ( <li key={todo.id}>{todo.title}</li> ))} </ul> ) }如果你不喜欢布尔值,也可以直接使用status字段:
function Todos() { const { status, data, error } = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList, }) if (status === 'pending') { return <span>Loading...</span> } if (status === 'error') { return <span>Error: {error.message}</span> } // also status === 'success', but "else" logic works, too return ( <ul> {data.map((todo) => ( <li key={todo.id}>{todo.title}</li> ))} </ul> ) }TypeScript 会在你依次检查了pending与error之后自动收窄data的类型,无需额外的非空断言。
从useQuery的类型定义(packages/preact-query/src/useQuery.ts)还可以看到一个细节:如果传入了initialData,会命中专门的重载,返回值类型变为DefinedUseQueryResult,此时data在类型上永远不会是undefined——即使后台重新获取失败,已有数据也会保留展示。
fetchStatus 与两种状态的区分
除了status,查询结果还提供fetchStatus属性,取值为:
fetchStatus === 'fetching':查询当前正在获取数据;fetchStatus === 'paused':查询想要获取数据但被暂停了(更多见网络模式相关指南);fetchStatus === 'idle':查询当前什么都没做。
为什么需要两套状态?
后台重新获取与 stale-while-revalidate(过期数据先展示、后台刷新)逻辑使得status与fetchStatus的所有组合都可能出现。例如:
- 一个
success状态的查询通常处于idle的 fetchStatus,但如果正在后台重新获取,则可能处于fetching; - 一个刚挂载、还没有数据的查询通常处于
pending状态 +fetchingfetchStatus,但如果没有网络连接,也可能变成paused。
因此请记住:查询可以处于pending状态但并没有真正在获取数据。作为经验法则:
status反映的是data的情况:我们有没有数据?fetchStatus反映的是queryFn的情况:它是否正在运行?
概念二:变更(Mutations)
与查询不同,变更(Mutation)通常用于创建/更新/删除数据或执行服务端副作用。为此,TanStack Query 导出了useMutationhook(Preact 版本位于 packages/preact-query/src/useMutation.ts,内部基于 query-core 的MutationObserver实现)。
useMutation 最小用法
下面是一个向服务端新增 todo 的变更示例:
function App() { const mutation = useMutation({ mutationFn: (newTodo) => { return axios.post('/todos', newTodo) }, }) return ( <div> {mutation.isPending ? ( 'Adding todo...' ) : ( <> {mutation.isError ? ( <div>An error occurred: {mutation.error.message}</div> ) : null} {mutation.isSuccess ? <div>Todo added!</div> : null} <button onClick={() => { mutation.mutate({ id: new Date(), title: 'Do Laundry' }) }} > Create Todo </button> </> )} </div> ) }任意时刻,一个变更只能处于以下四种状态之一:
| 状态 | 说明 |
|---|---|
isIdle或status === 'idle' | 变更当前处于空闲或全新的重置状态 |
isPending或status === 'pending' | 变更当前正在执行 |
isError或status === 'error' | 变更遇到了错误 |
isSuccess或status === 'success' | 变更成功且变更数据可用 |
根据所处状态还有更多信息:
error:处于error状态时,可通过error属性拿到错误对象;data:处于success状态时,可通过data属性拿到返回数据。
在上面的例子中你已经看到,可以通过mutate函数传入单个变量或对象作为mutationFn的参数。
单看变量传递,变更似乎没什么特别;但当它与onSuccess选项、QueryClient 的invalidateQueries方法以及 QueryClient 的setQueryData方法组合使用时,变更就会成为非常强大的工具——这正是第三个核心概念「查询失效」的用武之地。
注意:
mutate是异步函数。在 React 16 及更早版本中,不能直接在事件回调里使用它(受 React 事件池机制影响);如果需要在onSubmit中访问事件对象,必须把mutate包一层。例如下面这种写法在旧版本 React 中不生效:// This will not work in React 16 and earlier const CreateTodo = () => { const mutation = useMutation({ mutationFn: (event) => { event.preventDefault() return fetch('/api', new FormData(event.target)) }, }) return <form onSubmit={mutation.mutate}>...</form> }推荐的做法是在外层包装函数中先处理事件,再调用
mutate:// This will work const CreateTodo = () => { const mutation = useMutation({ mutationFn: (formData) => { return fetch('/api', formData) }, }) const onSubmit = (event) => { event.preventDefault() mutation.mutate(new FormData(event.target)) } return <form onSubmit={onSubmit}>...</form> }这一限制针对 React 旧版本的事件池行为;在 Preact 中同样建议采用包装函数这种稳妥写法。
使用 mutateAsync 获取 Promise
如果希望拿到一个「成功时 resolve、失败时 throw」的 Promise(例如用于组合副作用),请使用mutateAsync而不是mutate:
const mutation = useMutation({ mutationFn: addTodo }) try { const todo = await mutation.mutateAsync(todo) console.log(todo) } catch (error) { console.error(error) } finally { console.log('done') }重置变更状态
有时需要清除某个变更请求的error或data,此时可以使用reset函数:
const CreateTodo = () => { const [title, setTitle] = useState('') const mutation = useMutation({ mutationFn: createTodo }) const onCreateTodo = (e) => { e.preventDefault() mutation.mutate({ title }) } return ( <form onSubmit={onCreateTodo}> {mutation.error && ( <h5 onClick={() => mutation.reset()}>{mutation.error}</h5> )} <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} /> <br /> <button type="submit">Create Todo</button> </form> ) }变更副作用回调
useMutation提供了一些辅助选项,让你能在变更生命周期的任意阶段快速、方便地执行副作用。这些回调对「变更后失效并重新获取查询」以及「乐观更新」非常关键:
useMutation({ mutationFn: addTodo, onMutate: (variables, context) => { // A mutation is about to happen! // Optionally return a result containing data to use when for example rolling back return { id: 1 } }, onError: (error, variables, onMutateResult, context) => { // An error happened! console.log(`rolling back optimistic update with id ${onMutateResult.id}`) }, onSuccess: (data, variables, onMutateResult, context) => { // Boom baby! }, onSettled: (data, error, variables, onMutateResult, context) => { // Error or success... doesn't matter! }, })回调的串行等待:如果这些回调函数返回 Promise,那么它会被await,然后再调用下一个回调:
useMutation({ mutationFn: addTodo, onSuccess: async () => { console.log("I'm first!") }, onSettled: async () => { console.log("I'm second!") }, })调用mutate时追加回调:你可能会希望在mutate调用处触发useMutation定义之外的回调,用于执行组件特有的副作用。为此,可以在变更变量之后向mutate传入同样的回调选项(支持onSuccess、onError、onSettled)。请注意:如果组件在变更完成之前卸载,这些追加回调不会执行:
useMutation({ mutationFn: addTodo, onSuccess: (data, variables, onMutateResult, context) => { // I will fire first }, onError: (error, variables, onMutateResult, context) => { // I will fire first }, onSettled: (data, error, variables, onMutateResult, context) => { // I will fire first }, }) mutate(todo, { onSuccess: (data, variables, onMutateResult, context) => { // I will fire second! }, onError: (error, variables, onMutateResult, context) => { // I will fire second! }, onSettled: (data, error, variables, onMutateResult, context) => { // I will fire second! }, })连续变更时的回调差异
当处理连续多次变更时,onSuccess/onError/onSettled的表现存在细微差别:传给mutate的回调只会在组件仍然挂载时触发一次(因为每次调用mutate时 mutation observer 都会被移除并重新订阅);而useMutation层级的 handler 则会对每一次mutate调用都执行。
另外要注意:mutationFn通常都是异步的,因此多个变更完成的顺序可能与mutate调用的顺序不一致:
useMutation({ mutationFn: addTodo, onSuccess: (data, variables, onMutateResult, context) => { // Will be called 3 times }, }) const todos = ['Todo 1', 'Todo 2', 'Todo 3'] todos.forEach((todo) => { mutate(todo, { onSuccess: (data, variables, onMutateResult, context) => { // Will execute only once, for the last mutation (Todo 3), // regardless which mutation resolves first }, }) })重试(Retry)
默认情况下,TanStack Query不会在变更出错时重试,但可以通过retry选项开启:
const mutation = useMutation({ mutationFn: addTodo, retry: 3, })如果变更因设备离线而失败,它们会在设备重新联网后按原有顺序被重试。
持久化变更(Persist mutations)
需要时,变更可以被持久化到存储中并在之后恢复,这通过 hydration 相关函数完成。下面的示例演示了:定义带乐观更新的默认变更 → 发起变更 → 离线暂停后dehydrate序列化 → 应用重启后hydrate恢复 →resumePausedMutations恢复被暂停的变更:
const queryClient = new QueryClient() // Define the "addTodo" mutation queryClient.setMutationDefaults(['addTodo'], { mutationFn: addTodo, onMutate: async (variables, context) => { // Cancel current queries for the todos list await context.client.cancelQueries({ queryKey: ['todos'] }) // Create optimistic todo const optimisticTodo = { id: uuid(), title: variables.title } // Add optimistic todo to todos list context.client.setQueryData(['todos'], (old) => [...old, optimisticTodo]) // Return a result with the optimistic todo return { optimisticTodo } }, onSuccess: (result, variables, onMutateResult, context) => { // Replace optimistic todo in the todos list with the result context.client.setQueryData(['todos'], (old) => old.map((todo) => todo.id === onMutateResult.optimisticTodo.id ? result : todo, ), ) }, onError: (error, variables, onMutateResult, context) => { // Remove optimistic todo from the todos list context.client.setQueryData(['todos'], (old) => old.filter((todo) => todo.id !== onMutateResult.optimisticTodo.id), ) }, retry: 3, }) // Start mutation in some component: const mutation = useMutation({ mutationKey: ['addTodo'] }) mutation.mutate({ title: 'title' }) // If the mutation has been paused because the device is for example offline, // Then the paused mutation can be dehydrated when the application quits: const state = dehydrate(queryClient) // The mutation can then be hydrated again when the application is started: hydrate(queryClient, state) // Resume the paused mutations: queryClient.resumePausedMutations()持久化离线变更(Persisting Offline mutations)
如果配合 persistQueryClient 插件 持久化离线变更,那么除非提供了默认的 mutation 函数,否则页面重载后无法恢复这些变更。这是一个技术限制:持久化到外部存储时,只有变更的状态被序列化(函数无法被序列化)。hydration 之后,触发该变更的组件可能并未挂载,此时调用resumePausedMutations可能抛出No mutationFn found错误。解决办法是预先为相应 key 设置默认 mutation 函数:
const persister = createSyncStoragePersister({ storage: window.localStorage, }) const queryClient = new QueryClient({ defaultOptions: { queries: { gcTime: 1000 * 60 * 60 * 24, // 24 hours }, }, }) // we need a default mutation function so that paused mutations can resume after a page reload queryClient.setMutationDefaults(['todos'], { mutationFn: ({ id, data }) => { return api.updateTodo(id, data) }, }) export default function App() { return ( <PersistQueryClientProvider client={queryClient} persistOptions={{ persister }} onSuccess={() => { // resume mutations after initial restore from localStorage was successful queryClient.resumePausedMutations() }} > <RestOfTheApp /> </PersistQueryClientProvider> ) }关于查询与变更的完整离线方案,仓库中还有更详尽的 offline 示例(对应 React 版,Preact 版可参考同样结构)。
变更作用域(Mutation Scopes)
默认情况下,所有变更都是并行执行的——即使你对同一个 mutation 的.mutate()连续调用多次。可以通过给变更指定带id的scope来避免这一点:所有具有相同scope.id的变更将串行执行——当某个 scope 已有正在进行的变更时,新触发的变更会以isPaused: true状态进入队列,等轮到它们时自动恢复:
const mutation = useMutation({ mutationFn: addTodo, scope: { id: 'todo', }, })概念三:查询失效(Query Invalidation)
等待查询自然过期再重新获取并不总是可行——尤其是当你知道用户的操作已经让某条数据确凿地过期时。为此,QueryClient提供了invalidateQueries方法,可以智能地标记查询为过期并(按需)触发重新获取:
// Invalidate every query in the cache queryClient.invalidateQueries() // Invalidate every query with a key that starts with `todos` queryClient.invalidateQueries({ queryKey: ['todos'] })设计哲学:其他使用归一化缓存的库,会试图通过命令式更新或 schema 推断把新数据同步到本地查询;而 TanStack Query 反其道而行之——提供工具让你免于维护归一化缓存的手工劳动,转而采用定向失效 + 后台重新获取 + 最终原子更新的范式。
当通过invalidateQueries使一个查询失效时,会发生两件事:
- 它被标记为stale(过期),该过期状态会覆盖
useQuery及相关 hook 中配置的任何staleTime; - 如果该查询当前正被
useQuery及相关 hook 渲染,它还会在后台被重新获取。
前缀匹配
invalidateQueries、removeQueries等支持部分查询匹配的 API,都可以按 key 的前缀匹配多个查询,也可以非常精确地匹配单个查询。例如用todos前缀来失效所有 key 以todos开头的查询:
import { useQuery, useQueryClient } from '@tanstack/preact-query' // Get QueryClient from the context const queryClient = useQueryClient() queryClient.invalidateQueries({ queryKey: ['todos'] }) // Both queries below will be invalidated const todoListQuery = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList, }) const todoListQuery = useQuery({ queryKey: ['todos', { page: 1 }], queryFn: fetchTodoList, })你还可以通过传入更具体的 query key 来失效带特定变量的查询:
queryClient.invalidateQueries({ queryKey: ['todos', { type: 'done' }], }) // The query below will be invalidated const todoListQuery = useQuery({ queryKey: ['todos', { type: 'done' }], queryFn: fetchTodoList, }) // However, the following query below will NOT be invalidated const todoListQuery = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList, })exact 精确匹配
invalidateQueries非常灵活:如果只想失效没有任何附加变量或子 key的todos查询,可以传入exact: true:
queryClient.invalidateQueries({ queryKey: ['todos'], exact: true, }) // The query below will be invalidated const todoListQuery = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList, }) // However, the following query below will NOT be invalidated const todoListQuery = useQuery({ queryKey: ['todos', { type: 'done' }], queryFn: fetchTodoList, })predicate 谓词匹配
如果还想要更细粒度的控制,可以向invalidateQueries传入谓词函数。该函数会收到 query cache 中的每一个Query实例,由你返回true/false决定是否失效:
queryClient.invalidateQueries({ predicate: (query) => query.queryKey[0] === 'todos' && query.queryKey[1]?.version >= 10, }) // The query below will be invalidated const todoListQuery = useQuery({ queryKey: ['todos', { version: 20 }], queryFn: fetchTodoList, }) // The query below will be invalidated const todoListQuery = useQuery({ queryKey: ['todos', { version: 10 }], queryFn: fetchTodoList, }) // However, the following query below will NOT be invalidated const todoListQuery = useQuery({ queryKey: ['todos', { version: 5 }], queryFn: fetchTodoList, })更多查询过滤器
invalidateQueries等 API 接受的QueryFilters对象还支持type(active/inactive/all,默认all)、stale(匹配过期/新鲜查询)、fetchStatus(fetching/paused/idle)等过滤条件,详细说明可参考 Query Filters 文档。例如:
// Cancel all queries await queryClient.cancelQueries() // Remove all inactive queries that begin with `posts` in the key queryClient.removeQueries({ queryKey: ['posts'], type: 'inactive' }) // Refetch all active queries that begin with `posts` in the key await queryClient.refetchQueries({ queryKey: ['posts'], type: 'active' })完整可运行示例
将三大核心概念串起来,就得到了quick-start文档中的完整示例(原示例使用 React 的render与react-dom,对应 Preact 版本应改为从preact导入render):创建 client → Provider 注入 →useQuery拉取 todos 列表 →useMutation提交新 todo → 成功后invalidateQueries失效并后台刷新列表:
import { render } from 'preact' import { useQuery, useMutation, useQueryClient, QueryClient, QueryClientProvider, } from '@tanstack/preact-query' import { getTodos, postTodo } from '../my-api' // Create a client const queryClient = new QueryClient() function App() { return ( // Provide the client to your App <QueryClientProvider client={queryClient}> <Todos /> </QueryClientProvider> ) } function Todos() { // Access the client const queryClient = useQueryClient() // Queries const query = useQuery({ queryKey: ['todos'], queryFn: getTodos }) // Mutations const mutation = useMutation({ mutationFn: postTodo, onSuccess: () => { // Invalidate and refetch queryClient.invalidateQueries({ queryKey: ['todos'] }) }, }) return ( <div> <ul> {query.data?.map((todo) => ( <li key={todo.id}>{todo.title}</li> ))} </ul> <button onClick={() => { mutation.mutate({ id: Date.now(), title: 'Do Laundry', }) }} > Add Todo </button> </div> ) } render(<App />, document.getElementById('root'))这三大概念构成了@tanstack/preact-query的大部分核心功能,文档的后续章节会逐一深入讲解。
仓库中还提供了可直接运行的真实示例 examples/preact/simple,它是一个 Vite + Preact 项目,通过npm run dev即可启动。其入口 examples/preact/simple/src/index.tsx 展示了相同的三要素:创建QueryClient→QueryClientProvider注入 →useQuery拉取 GitHub API 仓库信息,并用isPending/error/isFetching处理加载、错误与后台更新提示:
import { render } from 'preact' import { QueryClient, QueryClientProvider, useQuery, } from '@tanstack/preact-query' const queryClient = new QueryClient() export function App() { return ( <QueryClientProvider client={queryClient}> <Example /> </QueryClientProvider> ) } const Example = () => { const { isPending, error, data, isFetching } = useQuery({ queryKey: ['repoData'], queryFn: async () => { const response = await fetch( 'https://api.github.com/repos/TanStack/query', ) return await response.json() }, }) if (isPending) return 'Loading...' if (error !== null) return 'An error has occurred: ' + error.message return ( <div> <h1>{data.full_name}</h1> <p>{data.description}</p> <strong>👀 {data.subscribers_count}</strong>{' '} <strong>✨ {data.stargazers_count}</strong>{' '} <strong>🍴 {data.forks_count}</strong> <div>{isFetching ? 'Updating...' : ''}</div> </div> ) } const app = document.getElementById('app') if (!app) throw new Error('Missing #app element') render(<App />, app)总结与下一步
至此你已经掌握了@tanstack/preact-query的完整上手链路:
- 安装:包管理器或 CDN 两种方式皆可,Preact 10+ 均兼容;
- 注入:
new QueryClient()+QueryClientProvider将 client 下发到整个组件树(Provider 挂载/卸载会自动mount/unmount,联动焦点与联网事件); - 查询:
useQuery({ queryKey, queryFn })声明式拉取数据,用status(pending/error/success)判断数据是否存在,用fetchStatus(fetching/paused/idle)判断queryFn是否在运行; - 变更:
useMutation+mutate/mutateAsync执行服务端写操作,利用onSuccess/onError/onSettled编排副作用,支持重试、持久化与作用域串行; - 失效:
queryClient.invalidateQueries({ queryKey })定向失效查询并触发后台重新获取,配合前缀匹配、exact与predicate实现精确控制。
继续深入的话,建议阅读 Queries 详解、Mutations 详解 与 Query Invalidation 详解,并结合 packages/preact-query/src 下的源码(如useQuery.ts、useMutation.ts、QueryClientProvider.tsx)理解适配层实现;Preact 框架的完整文档目录见 docs/framework/preact。
【免费下载链接】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.项目地址: https://gitcode.com/GitHub_Trending/qu/query
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考