首页
/ @tanstack/lit-query 中 CreateQueriesInput 类型详解:createQueriesController 元组推断的基石

@tanstack/lit-query 中 CreateQueriesInput 类型详解:createQueriesController 元组推断的基石

2026-09-07 16:58:27作者:鲍丁臣Ursa

本文围绕 CreateQueriesInput 这一类型别名展开,结合 packages/lit-query/src/createQueriesController.ts 的源码实现,讲清楚它的四个泛型参数、它在 createQueriesController 元组推断管线中的位置,以及如何在 Lit 组件里用它写出完整类型安全的多查询列表。读完后,你将能够理解“传入查询选项元组 → 逐元素推断结果元组”的完整类型机制,并能处理 queryOptions 辅助函数、selectinitialData、动态查询列表等进阶场景。

CreateQueriesInput 是什么

CreateQueriesInput 是 Lit Query 中 createQueriesController 的核心输入类型,其定义为:

type CreateQueriesInput<TQueryFnData, TError, TData, TQueryKey> = QueryObserverOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>;

该类型定义在 createQueriesController.ts,并通过 packages/lit-query/src/index.ts@tanstack/lit-query 包公开导出。它的语义是:createQueriesControllerqueries 数组里“单个查询”的选项类型

从源码注释看(L25-L30),它的设计意图有两条:

  • 它镜像(mirrors)@tanstack/query-coreQueryObserverOptions,因此所有单查询的选项(queryKeyqueryFnselectenabledretrystaleTime 等)都适用;
  • 它是“元组推断”(tuple inference)的原料——控制器接收一个查询选项元组后,会依据每个元素的 CreateQueriesInput 类型逐元素推导出对应的结果类型,实现“第 i 个查询的选项决定第 i 个结果的结构”。

四个泛型参数及其默认值

参考文档列出了 CreateQueriesInput 的四个类型参数,与源码 L31-L36 完全一致:

参数 默认值 含义
TQueryFnData unknown queryFn 返回的原始数据类型
TError DefaultError 查询失败时的错误类型
TData TQueryFnData 最终暴露给消费者的数据类型(通常经 select 转换后与 TQueryFnData 不同)
TQueryKey QueryKey(约束 extends QueryKey queryKey 的类型

TData = TQueryFnData 这个默认值很关键:如果你没有写 select,结果里的 data 就是 queryFn 的返回值类型;一旦提供 select: (data) => ...TData 会收敛为 select 的返回类型。这一点在源码的推断工具类型中体现为 unknown extends TData ? TQueryFnData : TDataL75L125)——即“如果 TData 没有被推断出来,就回退为原始查询数据”。

需要说明的是:CreateQueriesInput 本身是“理论输入类型”。在实际传给 createQueriesController 时,用户写的是普通对象字面量或 queryOptions(...) 返回值,真正的泛型推导发生在下文的内部工具类型中。

推断管线:从输入元素到结果元素

CreateQueriesInput 被一组非导出的内部工具类型消费,整条管线都在 createQueriesController.ts 内。

1. CreateQueriesInputForController:控制器视角的输入

type CreateQueriesInputForController<
  TQueryFnData = unknown,
  TError = DefaultError,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey,
> = OmitKeyof<CreateQueriesInput<TQueryFnData, TError, TData, TQueryKey>, never>

L38-L43。它基于 CreateQueriesInput 包了一层 OmitKeyof,是推断成功后统一使用的“归一化输入”类型。

2. GetCreateQueriesInput:识别用户写法的多个分支

GetCreateQueriesInputL49-L78)负责从用户实际传入的元素中提取泛型参数,按优先级依次尝试:

  1. queryOptions 品牌化选项:若元素带有 queryFnData / error / data 标签(由 queryOptions 通过 DataTag 写入 queryKey 的品牌类型),直接取标签中的 TQueryFnDataTErrorTData。这就是推荐用法——先用 queryOptions 定义、再放入 queries 数组,类型能无损穿透;
  2. 元组写法:依次匹配 [queryFnData, error, data][queryFnData, error][queryFnData] 三种显式三元组/二元组,用于不需要实际执行 queryFn 的类型声明场景;
  3. 普通对象写法:从 queryFn?: QueryFunction<infer TQueryFnData, infer TQueryKey> | SkipTokenForCreateQueriesselect?: (data: any) => infer TDatathrowOnError?: ThrowOnError<any, infer TError, any, any> 三个位置推断泛型,并对 TErrorTDataunknown 回退处理;
  4. 兜底:以上都不匹配时返回无参的 CreateQueriesInputForController(即全默认泛型)。

这里还有一个值得注意的细节:SkipTokenForCreateQueriesL47)是一个专门的 symbol 类型,与 query-core 的 skipToken 语义对应,使得 queryFn: skipToken 写法在类型层面同样被接受。

3. 结果侧:GetCreateQueriesResult 与 Defined 判定

结果类型由 GetCreateQueriesResultL100-L128)计算,内部再委托给 GetDefinedOrUndefinedCreateQueriesResultL80-L98)。后者的核心逻辑是根据 initialData 判定结果是否为“已定义”

  • 若元素没有 initialData,结果为普通 QueryObserverResult<TData, TError>data 可能为 undefined);
  • initialData 存在且其类型(或惰性函数返回值)可赋值给 TData,则升级为 DefinedQueryObserverResult<TData, TError>data 保证非 undefined);
  • 其余情况(如 initialData 是返回 unknown 的函数)回退为普通 QueryObserverResult

元组/对象两种写法走同一套判定,因此与输入侧的分支一一对应。

4. 元组递归:CreateQueriesOptions 与 CreateQueriesResults

最后一步是把“逐元素”的推断组织成整个数组:

  • CreateQueriesOptionsL130-L164):把输入元组 T 递归解构为 [infer Head, ...infer Tails],对每个 Head 应用 GetCreateQueriesInput,累积出归一化输入元组;
  • CreateQueriesResultsL170-L186):同样的递归结构,用 GetCreateQueriesResult 生成结果元组。

两个递归都受 MAXIMUM_DEPTH = 20L45)保护:当 TDepth['length'] extends MAXIMUM_DEPTH 时直接退化为 Array<CreateQueriesInputForController> / Array<QueryObserverResult>,避免超长元组导致的类型实例爆炸。

对于非元组的普通数组(如 array.map(...) 生成的 QueryObserverOptions[]),推断会走映射分支:输入侧统一为同构的 CreateQueriesInputForController<TQueryFnData, TError, TData, TQueryKey> 数组(L148-L163),结果侧则退化为索引映射 { [K in keyof T]: GetCreateQueriesResult<T[K]> }L186)——即得到“元素类型的并集数组”,而非逐位置元组。

实战:在 Lit 组件中使用 createQueriesController

CreateQueriesInput 的完整选项集决定了 queries 中每个元素可以写什么。createQueriesController 的签名(L701-L719)接收三参:hostoptions(或返回 options 的 getter)、可选的 queryClient;返回可调用的 QueriesResultAccessor,带 current 属性与 destroy() 方法。

静态查询列表 + combine

源码 JSDoc 给出的官方示例(L676-L699)展示了最典型的用法:

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>`
  }
}

其中 queries 里每个对象就是一组 CreateQueriesInput 实例;combineCreateQueriesControllerOptions 的可选字段,L195-L210)接收推断出的结果元组,可把它重塑成任意单一值。

响应式查询列表(Accessor)

queries 与整个 options 都接受 Accessor<T> = T | (() => T)(定义见 accessor.ts)。传 getter 时,控制器会在宿主每次更新时重新读取查询列表,让查询数量/键跟随组件状态变化。queries-controller.test.ts 中的 DeferredFieldsQueriesHost 即为此模式:

readonly queries = createQueriesController(this, () => ({
  queries: this.ids.map((id) => ({
    queryKey: ['deferred-fields-queries', id] as const,
    queryFn: async () => id,
    retry: false,
  })),
  combine: (results) => results.map((result) => result.status),
}))

判断“是否需要在宿主更新时刷新”的逻辑见 shouldRefreshOnHostUpdateoptions 是函数、或 options.queries 是函数时返回 true

推荐配合 queryOptions 使用

若希望跨文件共享查询定义、且让 queryKey 携带数据/错误类型标签(DataTag),应先用 queryOptions 定义。其运行时实现就是原样返回入参(L141-L143),纯粹为类型服务;而 GetCreateQueriesInput 的第一个分支正是为识别这种品牌化输入而设,因此 queryOptions 产出的元素能以最高保真度参与元组推断。

运行时如何处理这些输入

类型推断之外,每个 CreateQueriesInput 元素在运行时的处理链路如下(均可在 createQueriesController.ts 中定位):

  1. 默认值合并resolveQueriesOptionsL287-L312)先通过 readAccessor 解析 optionsqueries 两个 getter,再对每个查询调用 client.defaultQueryOptions(query),把客户端级默认选项合并进来,并打上 _optimisticResults = 'optimistic' 标记;
  2. 观察器订阅:合并后的 QueryObserverOptions 数组交给 query-core 的 QueriesObservercombine 作为 QueriesObserverOptions['combine'] 传入(L412-L417);
  3. 占位结果:在 QueryClient 尚未就绪(例如还没挂进 QueryClientProvider 子树)时,createPlaceholderQueryObserverResultL257-L285)会为每个输入查询合成一个结果:没有 initialData 时是 isPending: true 的占位;有 initialData 时则直接以 isSuccess: true 呈现(同样会应用 selectinitialDataUpdatedAt)。测试 queries-controller.test.ts 的 LC-QUERIES-01 验证了“先读占位、Provider 连接后再取到真实数据”的完整时序;
  4. 细粒度通知trackResultL580-L602)把每个结果交由对应 QueryObserver 做属性级追踪,宿主只有在实际访问过的属性变化时才触发更新。

类型推断的测试证据

type-inference.test.tsL1 用例用 expectTypeOf 固化了本文描述的推断行为:

  • 元组保序推断queries: [{ queryFn: async () => 1 }, { queryFn: async () => 'x' }] as const 的结果类型是 [QueryObserverResult<number>, QueryObserverResult<string>]tupleData[0].datanumber | undefinedtupleData[1].datastring | undefined
  • combine 内逐位置可用combine: (result) => ({ first: result[0].data, second: result[1].data }) 中两个位置的类型均正确;
  • initialData 触发 Defined 结果:带 initialData: { id: 0, name: 'Seed' }queryOptions 元素,其 data 被推断为确定的 { id: number; name: string },而非 | undefined
  • 映射数组退化为并集[...numberQueries, booleanQuery] 这种非元组数组中,mappedQueriesData[0].datanumber | boolean | undefined,与上文的索引映射规则一致。

小结

CreateQueriesInput 虽然只是一个四参数别名,但它是 Lit Query 多查询控制器类型系统的入口:它镜像 QueryObserverOptions 保证了选项能力完整,而围绕它的 GetCreateQueriesInputGetCreateQueriesResultCreateQueriesOptions/CreateQueriesResults 递归(配合 MAXIMUM_DEPTH = 20SkipTokenForCreateQueries)把“输入元组 → 结果元组”的逐位置推断变成了类型层面的自动完成。日常使用上,优先用 queryOptions 定义查询元素、需要响应式数量时用 getter 形式的 queries、需要单一返回值时用 combine,即可获得从选项到渲染数据的端到端类型安全。

参考路径

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

项目优选

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