尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

TanStack Lit Query 的 MutationResultAccessor:类型定义、四个方法行为与源码实现全解析

TanStack Lit Query 的 MutationResultAccessor:类型定义、四个方法行为与源码实现全解析 TanStack Lit Query 的 MutationResultAccessor:类型定义、四个方法行为与源码实现全解析【免费下载链接】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本文基于 TanStack Query 仓库中 Lit 适配层的类型参考文档,系统讲解MutationResultAccessor这一核心返回类型的完整定义:它由createMutationController返回,既是可直接调用的取值函数,又暴露mutate、mutateAsync、reset、destroy四个方法。读完本文,你将理解该类型如何桥接 Lit 的响应式控制器机制与tanstack/query-core的MutationObserver,掌握 mutation 各状态的读取方式、无QueryClient时的降级行为,以及一套可运行的完整使用示例。MutationResultAccessor 的类型定义该类型定义于 createMutationController.ts,官方参考文档见 MutationResultAccessor.md:type MutationResultAccessorTData, TError, TVariables, TOnMutateResult ValueAccessorMutationObserverResultTData, TError, TVariables, TOnMutateResult { mutate: ( ...args: Parameters MutateFunctionTData, TError, TVariables, TOnMutateResult ) void mutateAsync: MutationObserverResult TData, TError, TVariables, TOnMutateResult [mutate] reset: MutationObserverResult TData, TError, TVariables, TOnMutateResult [reset] destroy: () void }从结构上看,它由两部分交叉类型组成:ValueAccessorMutationObserverResult...:一个可调用访问器,调用它或读取其current属性都能拿到最新的 mutation 结果;附加方法集:mutate、mutateAsync、reset、destroy,全部委托给当前激活的 mutation observer。其输入类型CreateMutationOptions就是MutationObserverOptions的 Lit 适配别名,完整参数说明见 CreateMutationOptions.md,而值或取值函数这一Accessor语义的来源见 types.ts。两种读取结果的方式:调用与current属性ValueAccessor定义在 accessor.ts:export type ValueAccessorT (() T) { readonly current: T } export function createValueAccessorT(getter: () T): ValueAccessorT { const accessor (() getter()) as ValueAccessorT Object.defineProperty(accessor, current, { get: getter, enumerable: true, }) return accessor }也就是说,ValueAccessorT本质上是一个函数,同时挂载了一个只读的currentgetter,两者执行同一个 getter,返回值完全一致。在渲染代码中可以任选其一:const mutation this.addTodo() // 调用 const sameResult this.addTodo.current // 读取属性,等价在 createMutationController.ts 中,工厂函数正是通过Object.assign把这个取值函数与四个方法拼装成最终的MutationResultAccessor:const controller new MutationController(host, options, queryClient) return Object.assign( createValueAccessor(() controller.current), { mutate: controller.mutate, mutateAsync: controller.mutateAsync, reset: controller.reset, destroy: () controller.destroy(), }, )注意取值闭包是() controller.current,而controller.current来自基类BaseController的currentgetter,它带有一个关键防护(见下文无 QueryClient 时的行为一节)。四个附加方法的行为与实现证据mutate:同步触发,吞掉返回的 Promisemutate: ( ...args: Parameters MutateFunctionTData, TError, TVariables, TOnMutateResult ) voidmutate启动 mutation 并吞掉返回的 Promise,返回void。源码实现(createMutationController.ts):mutate (...args): void { if (!this.syncClient() || !this.observer) { throw createMissingQueryClientError() } void this.observer.mutate(args[0] as TVariables, args[1]).catch(() { // Intentionally swallow in sync mutate path. }) }两个关键行为:若控制器无法解析出QueryClient,会同步抛出No QueryClient available错误;mutation 执行失败时不会触发未处理的 Promise 拒绝——错误通过 observer 订阅反映到结果对象上(isError/error)。这一点被测试 M11 明确验证:expect(() mutation.mutate(-1)).not.toThrow(),随后mutation().isError为true(mutation-controller.test.ts)。参数签名ParametersMutateFunction...意味着mutate接受mutationFn所需的全部参数(通常第一个是TVariables,第二个是可选的MutateOptions)。mutateAsync:返回 observer 的 PromisemutateAsync: MutationObserverResultTData, TError, TVariables, TOnMutateResult[mutate]mutateAsync与mutate的区别在于:它返回observer 的 Promise,成功时 resolve 为TData,失败时 reject 为TError;当无法解析QueryClient时,不是抛错而是返回一个 rejected promise(createMutationController.ts)。测试 LC-MUT-03 同时验证了这两种失败形态:expect(() consumer.mutation.mutate(1)).toThrow(/No QueryClient available/) await expect(consumer.mutation.mutateAsync(1)).rejects.toThrow(/No QueryClient available/)回调顺序也有确定性保证:测试 M12 断言onSuccess→onSettled→onError→onSettled的触发顺序(mutation-controller.test.ts)。reset:回到 idle 基线reset: MutationObserverResultTData, TError, TVariables, TOnMutateResult[reset]reset把 mutation observer 重置回 idle 状态。实现(createMutationController.ts)在调用observer.reset()后立即用getCurrentResult()刷新结果;若没有 client 或 observer,则静默返回而不是抛错。测试 M10 验证了重置后的完整基线:isIdle为true、isPaused为false、isError为false、error为null、data为undefined(mutation-controller.test.ts)。destroy:从 Lit 宿主摘除控制器destroy: () voiddestroy将控制器从其 Lit 宿主移除并取消 observer 订阅。基类实现位于 BaseController.ts:destroy(): void { if (this.destroyed) { return } this.destroyed true this.connected false this.connectionAttempt 1 this.clearContextClient() // ... this.onDisconnected() if (removeController in this.host) { this.host.removeController(this) } }注意幂等性:重复调用destroy()不会抛错。onDisconnected在 mutation 控制器中的行为是取消 observer 订阅并清理 client 状态(createMutationController.ts)。四个类型参数参数含义说明TDatamutation 成功后的数据类型即mutationFn的返回类型TError错误类型默认DefaultErrorTVariables传给mutationFn的变量类型默认voidTOnMutateResultonMutate回调的返回类型会作为context传入onError/onSuccess/onSettled这四个参数与MutationObserverResult保持一致,因此在渲染层读取data、error、variables时能获得完整类型。工厂函数的默认泛型参数在 createMutationController.ts 中声明:TData unknown、TError DefaultError、TVariables void、TOnMutateResult unknown。Mutation 结果对象与状态机MutationResultAccessor每次取值返回的都是MutationObserverResult,其核心状态标志包括(见 mutations.md):isIdle/status idle:未执行过或已reset;isPending/status pending:mutation 正在运行;isError/status error:失败,error可用;isSuccess/status success:完成,data可用。此外还有data、error、variables、failureCount、context、submittedAt等字段。测试 M9 覆盖了完整的 idle → pending → success → pending → error 迁移路径(mutation-controller.test.ts)。无 QueryClient 时的行为:idle 占位与确定性失败createMutationController接受可选的第三个参数queryClient。若省略,则从最近的QueryClientProvider解析;若两处都没有,控制器进入一种确定性的 missing-client 状态,而不是崩溃。这个机制在源码中分三层:idle 占位结果。构造时结果初始化为createIdleMutationResult()(createMutationController.ts),其中isIdle为true,且占位的mutate是一个直接返回Promise.reject(createMissingQueryClientError())的函数,reset是空操作。因此 provider 连接之前读取结果不会抛错,只是状态恒为 idle。client 解析状态机。基类 BaseController.ts 定义了四态:pre-connect、awaiting-context、bound、missing。currentgetter 只在状态为missing时抛错(BaseController.ts):get current(): TResult { if (this.queryClientResolutionState missing) { throw createMissingQueryClientError() } return this.result }pre-connect(尚未连接)与missing(已连接但找不到 provider)的区分,保证了provider 晚到的场景下访问器仍可用。晚到的 provider 可无重建恢复。测试 LC-MUT-04 验证:先处于 missing 状态的消费者,在被插入一个合法的QueryClientProvider后,mutateAsync直接成功,无需重建控制器(mutation-controller.test.ts)。显式传入的 client 则始终优先于 provider 上下文,LC-MUT-02 通过比较两个 client 的 mutation 缓存证明了这一点(mutation-controller.test.ts)。结果如何驱动 Lit 重渲染结果更新到 UI 的链路是:observer 订阅 →setResult→ 宿主requestUpdate。MutationController.subscribe通过observer.subscribe((next) this.setResult(next))接管 observer 通知(createMutationController.ts);基类的setResult用Object.is做浅比较去重,并在非 host-update 阶段通过微任务队列调用host.requestUpdate()(BaseController.ts);若options以函数形式传入(Accessor),onHostUpdate会在每次宿主更新时重新读取 options,因此 mutation 的回调可以依赖最新的宿主状态——测试 AREACT-02 验证了回调闭包会跟随宿主版本刷新(mutation-controller.test.ts)。完整使用示例下面是 mutations.md 中的标准用法,展示了MutationResultAccessor在真实组件中的位置:import { LitElement, html } from lit import { QueryClient, QueryClientProvider, createMutationController, createQueryController, } from tanstack/lit-query const queryClient new QueryClient() class AppQueryProvider extends QueryClientProvider { constructor() { super() this.client queryClient } } customElements.define(app-query-provider, AppQueryProvider) class TodosView extends LitElement { private readonly todos createQueryController(this, { queryKey: [todos], queryFn: fetchTodos, }) private readonly addTodo createMutationController(this, { mutationFn: createTodo, onSuccess: async () { await queryClient.invalidateQueries({ queryKey: [todos] }) }, }) render() { const query this.todos() const mutation this.addTodo() // MutationResultAccessor 的调用读法 const todos query.data ?? [] return html ${mutation.isError ? htmlp${mutation.error.message}/p : null} ${mutation.isSuccess ? htmlpTodo added/p : null} button ?disabled${mutation.isPending} click${() this.addTodo.mutate({ title: Write mutation docs })} ${mutation.isPending ? Adding... : Add Todo} /button ul ${todos.map((todo) htmlli${todo.title}/li)} /ul } } customElements.define(todos-view, TodosView)app-query-provider todos-view/todos-view /app-query-provider几个要点:控制器作为private readonly字段在构造期创建一次,render中通过调用this.addTodo()读取最新结果;mutate携带变量触发提交,mutation.isPending驱动按钮禁用;需要 Promise 语义时改用await this.addTodo.mutateAsync({ title })并自行捕获错误;显示清除错误按钮时调用this.addTodo.reset()。源码 JSDoc 中还有一个更精简的最小示例(createMutationController.ts):class AddTodoForm extends LitElement { private readonly addTodo createMutationController(this, { mutationFn: (title: string) fetch(/api/todos, { method: POST, body: JSON.stringify({ title }) }), }) render() { const mutation this.addTodo() return html button ?disabled${mutation.isPending} click${() this.addTodo.mutate(Ship docs)} Add todo /button } }小结MutationResultAccessor是 Lit Query 中 mutation 的全部入口:一次取值即得完整结果(调用或.current),四个方法分别覆盖同步提交(mutate)、Promise 提交(mutateAsync)、状态重置(reset)和生命周期清理(destroy)。结合 accessor.ts 的ValueAccessor实现、BaseController.ts 的 client 解析状态机,以及 mutation-controller.test.ts 中 LC-MUT/M 系列用例,可以确认该类型在 provider 缺失、晚到、显式 client 等边界场景下都有确定、可测试的行为,适合作为 Lit 组件中管理服务端副作用的标准返回值类型。【免费下载链接】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),仅供参考
返回列表