news 2026/9/10 9:46:10

TanStack Query Preact 快速上手:Queries、Mutations 与 Query Invalidation 三大核心概念实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TanStack Query Preact 快速上手:Queries、Mutations 与 Query Invalidation 三大核心概念实战

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-query
yarn add @tanstack/preact-query
bun add @tanstack/preact-query
deno 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)做了两件事:

  1. 通过 Preact Context 下发 clientQueryClientProvider内部使用createContext创建QueryClientContext,并用<QueryClientContext.Provider value={client}>包裹 children,使整个子树都能通过useQueryClient读取到该实例;
  2. 绑定客户端生命周期:组件挂载时调用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',因此QueryClientQueryCacheMutationCache等核心类都从同一个包导入即可,无需额外安装依赖。

三大核心概念总览

@tanstack/preact-query的核心功能由以下三个概念构成(它们也是 docs/framework/preact/quick-start.md 一文的骨架):

  • Queries(查询):声明式地依赖某个异步数据源,用唯一 key 缓存、共享数据;
  • Mutations(变更):用于创建/更新/删除数据或执行服务端副作用;
  • Query Invalidation(查询失效):当数据确知过期时,智能地标记查询失效并触发后台重新获取。

下面依次深入这三个概念。

概念一:查询(Queries)

查询的本质与最小用法

一个查询是对某个异步数据源的声明式依赖,它与一个唯一 key绑定。任何基于 Promise 的方法(包括 GET 和 POST)都可以用于从服务端获取数据;但请注意:如果某个方法会修改服务端数据,官方推荐改用 Mutations 而不是 Query。

要订阅一个查询,在组件或自定义 hook 中调用useQuery,至少传入两个参数:

  • 查询的唯一 keyqueryKey);
  • 返回 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对象包含模板渲染所需的全部信息。任意时刻,一个查询只能处于以下三种状态之一:

状态说明
isPendingstatus === 'pending'查询还没有数据
isErrorstatus === 'error'查询遇到了错误
isSuccessstatus === '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 会在你依次检查了pendingerror之后自动收窄data的类型,无需额外的非空断言。

useQuery的类型定义(packages/preact-query/src/useQuery.ts)还可以看到一个细节:如果传入了initialData,会命中专门的重载,返回值类型变为DefinedUseQueryResult,此时data在类型上永远不会是undefined——即使后台重新获取失败,已有数据也会保留展示。

fetchStatus 与两种状态的区分

除了status,查询结果还提供fetchStatus属性,取值为:

  • fetchStatus === 'fetching':查询当前正在获取数据;
  • fetchStatus === 'paused':查询想要获取数据但被暂停了(更多见网络模式相关指南);
  • fetchStatus === 'idle':查询当前什么都没做。

为什么需要两套状态?

后台重新获取与 stale-while-revalidate(过期数据先展示、后台刷新)逻辑使得statusfetchStatus的所有组合都可能出现。例如:

  • 一个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> ) }

任意时刻,一个变更只能处于以下四种状态之一:

状态说明
isIdlestatus === 'idle'变更当前处于空闲或全新的重置状态
isPendingstatus === 'pending'变更当前正在执行
isErrorstatus === 'error'变更遇到了错误
isSuccessstatus === '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') }

重置变更状态

有时需要清除某个变更请求的errordata,此时可以使用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传入同样的回调选项(支持onSuccessonErroronSettled)。请注意:如果组件在变更完成之前卸载,这些追加回调不会执行:

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()连续调用多次。可以通过给变更指定带idscope来避免这一点:所有具有相同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使一个查询失效时,会发生两件事:

  1. 它被标记为stale(过期),该过期状态会覆盖useQuery及相关 hook 中配置的任何staleTime
  2. 如果该查询当前正被useQuery及相关 hook 渲染,它还会在后台被重新获取

前缀匹配

invalidateQueriesremoveQueries等支持部分查询匹配的 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非常灵活:如果只想失效没有任何附加变量或子 keytodos查询,可以传入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对象还支持typeactive/inactive/all,默认all)、stale(匹配过期/新鲜查询)、fetchStatusfetching/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 的renderreact-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 展示了相同的三要素:创建QueryClientQueryClientProvider注入 →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的完整上手链路:

  1. 安装:包管理器或 CDN 两种方式皆可,Preact 10+ 均兼容;
  2. 注入new QueryClient()+QueryClientProvider将 client 下发到整个组件树(Provider 挂载/卸载会自动mount/unmount,联动焦点与联网事件);
  3. 查询useQuery({ queryKey, queryFn })声明式拉取数据,用statuspending/error/success)判断数据是否存在,用fetchStatusfetching/paused/idle)判断queryFn是否在运行;
  4. 变更useMutation+mutate/mutateAsync执行服务端写操作,利用onSuccess/onError/onSettled编排副作用,支持重试、持久化与作用域串行;
  5. 失效queryClient.invalidateQueries({ queryKey })定向失效查询并触发后台重新获取,配合前缀匹配、exactpredicate实现精确控制。

继续深入的话,建议阅读 Queries 详解、Mutations 详解 与 Query Invalidation 详解,并结合 packages/preact-query/src 下的源码(如useQuery.tsuseMutation.tsQueryClientProvider.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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 9:45:41

51单片机LCD12864计算器仿真:驱动时序与状态机实战

简介&#xff1a;本资源是一套基于51单片机与LCD12864液晶屏实现的简易计算器Proteus仿真工程&#xff0c;面向嵌入式初学者、单片机课程设计学生及电子类实训人员&#xff0c;旨在帮助理解按键扫描、LCD驱动、算术运算逻辑与软硬件协同仿真实现。压缩包共29个文件&#xff0c;…

作者头像 李华
网站建设 2026/9/10 9:44:31

终极指南:Telegram.Bot.Examples控制台应用详解与实战案例

终极指南&#xff1a;Telegram.Bot.Examples控制台应用详解与实战案例 Telegram.Bot.Examples是基于Telegram.Bot C#库的官方示例项目&#xff0c;提供了从基础到高级的多种Telegram机器人实现方案。本文将聚焦控制台应用场景&#xff0c;通过两个核心示例项目展示如何快速构建…

作者头像 李华
网站建设 2026/9/10 9:42:45

如何用 Social-Analyzer 一个用户名排查上千社媒平台:实战笔记

如何用 Social-Analyzer 一个用户名排查上千社媒平台&#xff1a;实战笔记 【免费下载链接】social-analyzer API, CLI, and Web App for analyzing and finding a persons profile in 1000 social media \ websites 项目地址: https://gitcode.com/GitHub_Trending/so/socia…

作者头像 李华