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

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

2026-09-08 19:44:11作者:袁立春Spencer

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-querypackages/preact-query/src/index.ts 中直接 export * from '@tanstack/query-core',因此 QueryClientQueryCacheMutationCache 等核心类都从同一个包导入即可,无需额外安装依赖。

三大核心概念总览

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

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

概念一:查询(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 状态 + fetching fetchStatus,但如果没有网络连接,也可能变成 paused

因此请记住:查询可以处于 pending 状态但并没有真正在获取数据。作为经验法则:

  • status 反映的是 data 的情况:我们有没有数据?
  • fetchStatus 反映的是 queryFn 的情况:它是否正在运行?

概念二:变更(Mutations)

与查询不同,变更(Mutation)通常用于创建/更新/删除数据或执行服务端副作用。为此,TanStack Query 导出了 useMutation hook(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

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391