TanStack Query lit-query 类型参考:QueriesControllerOptions 详解与 createQueriesController 选项机制
本篇技术指南围绕 @tanstack/lit-query 的类型别名 QueriesControllerOptions 展开,它是 createQueriesController 所接受的选项类型。读完本文,你能准确理解该类型的两层结构(Accessor 包装 + CreateQueriesControllerOptions 本体)、两个类型参数的含义与默认值,并掌握在 Lit 组件中编写静态/动态并行查询、使用 combine 聚合多查询结果的完整用法。
类型定义与出处
QueriesControllerOptions 的完整定义为:
type QueriesControllerOptions<TQueryOptions, TCombinedResult> = Accessor<CreateQueriesControllerOptions<TQueryOptions, TCombinedResult>>;
该类型定义于 packages/lit-query/src/types.ts,源码中带有明确的文档注释:"Accessor-wrapped options accepted by createQueriesController"(供 createQueriesController 使用的、被 Accessor 包装的选项)。
从定义结构看,它由两个正交部分组成:
- 外层
Accessor<T>包装:允许调用方直接传入选项对象,或传入一个零参 getter 函数,让选项跟随 Lit 组件的响应式状态在宿主更新时被重新求值; - 内层
CreateQueriesControllerOptions<TQueryOptions, TCombinedResult>:真正的选项本体,包含queries(查询列表)与可选的combine(结果聚合函数)两个字段。
这种"双层可访问"设计是 lit-query 控制器 API 的统一约定,Accessor 的类型本体与运行时读取逻辑见 packages/lit-query/src/accessor.ts:
export type Accessor<T> = T | (() => T)
export function readAccessor<T>(value: Accessor<T>): T {
return typeof value === 'function' ? (value as () => T)() : value
}
类型参数
TQueryOptions
约束为 Array<any>,默认值 Array<any>。它是传入 queries 的查询选项数组(或元组)的静态类型,是结果类型推断的输入。源码中每个数组元素对应 CreateQueriesInput(即 @tanstack/query-core 的 QueryObserverOptions 形态),见 packages/lit-query/src/createQueriesController.ts。
TCombinedResult
默认值为 CreateQueriesResults<TQueryOptions>。即:如果不显式指定该类型参数(也未传 combine),返回结果就是一个按输入顺序推断出的查询结果元组;如果传了 combine,它则是 combine 的返回值类型。CreateQueriesResults 通过递归映射每个查询项的 queryFn/select/initialData 等信息推断出对应的 QueryObserverResult,其递归推断受 MAXIMUM_DEPTH = 20 深度限制,超限后退化为 Array<QueryObserverResult>,见 packages/lit-query/src/createQueriesController.ts。
内层选项 CreateQueriesControllerOptions 的字段
剥去 Accessor 包装后,实际可配置的字段定义在 packages/lit-query/src/createQueriesController.ts:
export type CreateQueriesControllerOptions<
TQueryOptions extends Array<any> = Array<any>,
TCombinedResult = CreateQueriesResults<TQueryOptions>,
> = {
/** Query options to observe, or a getter that returns the current options. */
queries: Accessor<
| readonly [...CreateQueriesOptions<TQueryOptions>]
| readonly [
{ [K in keyof TQueryOptions]: GetCreateQueriesInput<TQueryOptions[K]> }
]
>
/** Optional function that combines the query result array into one value. */
combine?: (result: CreateQueriesResults<TQueryOptions>) => TCombinedResult
}
各字段说明:
queries(必填):要观察的查询选项列表。注意它本身也是Accessor类型——即使外层options是静态对象,queries也可以单独写成 getter 以跟随响应式状态;反之亦然。两种嵌套组合都支持:options是对象 +queries是 getter;options本身是 getter(此时整个选项在宿主更新时重新读取)。
combine(可选):将"查询结果数组"折叠为单个派生值的函数。不传时,QueriesControllerOptions的返回类型即TCombinedResult默认值CreateQueriesResults<TQueryOptions>(结果元组);传入时,其返回值即为最终类型。
从源码结构看,queries 的类型签名是一个 readonly 的联合类型:既接受递归映射后的数组形式(CreateQueriesOptions),也接受按 keyof TQueryOptions 映射的元组形式,这正是"输入是元组时结果也推断为对应位置元组"的类型机制来源。
使用示例
静态并行查询 + combine 聚合
以下示例来自 packages/lit-query/src/createQueriesController.ts 中的官方 @example 注释,options 以静态对象传入(合法 Accessor 分支之一):
import { LitElement, html } from 'lit'
import { createQueriesController } from '@tanstack/lit-query'
class DashboardView extends LitElement {
private readonly dashboard = createQueriesController(this, {
queries: [
{ queryKey: ['stats'], queryFn: fetchStats },
{ queryKey: ['projects'], queryFn: fetchProjects },
],
combine: ([stats, projects]) => ({
stats: stats.data,
projects: projects.data ?? [],
isPending: stats.isPending || projects.isPending,
}),
})
render() {
const dashboard = this.dashboard()
return html`<p>Projects: ${dashboard.projects.length}</p>`
}
}
动态查询列表(options 作为 getter)
当查询数量/内容依赖宿主响应式字段时,将整个 options 写成 getter。该用法收录于 docs/framework/lit/guides/parallel-queries.md:
import { LitElement, html } from 'lit'
import { createQueriesController } from '@tanstack/lit-query'
class UsersDetails extends LitElement {
static properties = {
userIds: { attribute: false },
}
userIds: Array<string> = []
private readonly users = createQueriesController(this, () => ({
queries: this.userIds.map((id) => ({
queryKey: ['user', id],
queryFn: () => fetchUserById(id),
})),
}))
render() {
const userQueries = this.users()
return html`
<ul>
${userQueries.map((query, index) => {
if (query.isPending) return html`<li>Loading...</li>`
if (query.isError) return html`<li>Error loading user</li>`
return html`<li>${this.userIds[index]}: ${query.data.name}</li>`
})}
</ul>
`
}
}
结果顺序与输入查询顺序严格一致;若 queries 数组中出现重复的 queryKey,这些条目会共享缓存数据——如果每行渲染需要独立查询状态,应先去重。
源码级机制:选项在运行期如何被消费
QueriesControllerOptions 不只是类型层面的约定,QueriesController 类(packages/lit-query/src/createQueriesController.ts)在运行时对其做了如下消费:
1. 两层 Accessor 的解析。resolveQueriesOptions 先 readAccessor(optionsAccessor) 得到内层选项对象,再 readAccessor(resolvedOptions.queries) 得到实际查询数组,随后逐项经过 client.defaultQueryOptions(...) 应用全局默认配置并打上 _optimisticResults: 'optimistic' 标记(乐观结果,避免初次订阅时因缓存未就绪而抖动),最后交给 QueriesObserver 建立对多个查询的观察,见 packages/lit-query/src/createQueriesController.ts。
2. 何时重新读取选项。shouldRefreshOnHostUpdate 的判定逻辑直接对应类型定义中两处 Accessor 位置:options 是函数,或 options.queries 是函数,任一成立,控制器就会在宿主更新(onHostUpdate)时重新解析选项并调用 observer.setQueries 同步查询列表,见 packages/lit-query/src/createQueriesController.ts。这就是"类型上允许函数、运行时保证响应式刷新"的闭环。
3. QueryClient 解析与占位结果。未显式传入 queryClient 时,控制器从最近的 QueryClientProvider 上下文解析;若客户端尚未就绪,会先为每个查询生成占位 QueryObserverResult(有 initialData 时直接映射为成功态),并在构造完成后重试初始化,见 packages/lit-query/src/createQueriesController.ts 与 docs/framework/lit/reference/functions/createQueriesController.md 中对 queryClient 参数的说明。
4. 返回值的形态。createQueriesController 返回 QueriesResultAccessor<TCombinedResult>——一个可调用函数,同时暴露 current 属性与 destroy() 方法;combine 的返回值会经过 replaceEqualDeep 做深比较去重,只有实际内容变化才触发宿主更新,见 packages/lit-query/src/createQueriesController.ts。
行为层面的覆盖可参考测试文件 packages/lit-query/src/tests/queries-controller.test.ts 与类型推断测试 packages/lit-query/src/tests/type-inference.test.ts。
与其他类型的关系
CreateQueriesControllerOptions:被本类型解包后的选项本体,见 docs/framework/lit/reference/type-aliases/CreateQueriesControllerOptions.md;Accessor:外层包装类型,定义于 packages/lit-query/src/accessor.ts;QueriesResultAccessor:createQueriesController的返回类型,见 docs/framework/lit/reference/type-aliases/QueriesResultAccessor.md;- 函数参考:docs/framework/lit/reference/functions/createQueriesController.md 给出了该函数完整的参数、返回值与示例说明。
小结
QueriesControllerOptions<TQueryOptions, TCombinedResult> 是 lit-query 并行查询入口 createQueriesController 的类型契约:外层 Accessor 决定了"选项可以静态传入、也可以随宿主状态动态求值",内层 queries + combine 决定了"观察哪些查询、结果如何聚合",TQueryOptions 与 TCombinedResult 两个类型参数则通过元组递归推断把输入选项映射为精确的返回类型(无 combine 时为结果元组,有 combine 时为其返回值)。在编写 Lit 组件的动态并行查询时,理解这层结构即可正确使用静态选项、getter 选项与结果聚合三种模式。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00