技能使用指南

query-layer(查询层模式)Skill 是什么?如何下载使用?

查询层模式 Skill 基于 TanStack Query 搭建 UI 与服务层之间的反应式桥梁,内置三层错误转换、运行时依赖注入、双重接口与乐观更新缓存。本文详解其功能特点、安装验证步骤、分步使用指南、与原生方案及竞品对比、典型场景与常见问题,助你快速落地统一的数据层规范,提升前端开发效率。

查询层模式Skillquery-layer
23 个章节 约 20 分钟阅读 AI 智能生成

查询层模式Skill:把 TanStack Query、错误转换与依赖注入打包成一套可复用的前端数据桥梁

核心结论速览

  • 它是什么:查询层模式(query-layer)是一套「架构型」AI 技能,教 AI 助手按统一范式,在 UI 组件与服务层之间搭出一层反应式桥梁,技术栈是 TanStack Query + WellCrafted 工厂 + 运行时依赖注入。
  • 它解决什么问题:服务层的 TaggedError 直接漏到界面、缓存与乐观更新各写各的、切换服务实现要改一堆组件——这些「数据层脏活」被统一收敛到查询层。
  • 它的差异化:明确给出「三层错误流」「双重接口(.options 反应式 / .execute() 命令式)」「查询键层级化」「乐观更新」四套可直接照抄的写法,并附反模式提醒(禁止双重包装错误)。
  • 谁最需要它:使用 Svelte + TanStack Query、正在做「服务层 / 查询层 / UI 层」分层的开发者或团队,尤其是需要多服务商切换(如 OpenAI / Groq)的项目。
  • 上手成本:概念门槛中等(需理解 Result 类型、访问器函数、查询键),但一旦落地,新增一个查询或突变基本是「填空式」操作。

该技能叫什么?如何下载安装 AI 技能提升开发效率?

基本信息

项目 内容
技能中文名 查询层模式
技能英文名 query-layer
技能 ID 11868
作者 epicenter
版本 1.1
定位 前端架构模式 / 数据层设计技能

它到底解决什么问题

在中大型前端项目里,UI 组件和服务层之间往往缺一层「正经的胶水」:组件直接 await services.xxx(),于是缓存、重试、加载态、错误提示、乐观更新全散落在各个组件里;服务返回的是领域错误,界面需要的是「能直接弹吐司的错误」;用户一改设置里的服务商(OpenAI 换成 Groq),调用它的十几个组件都得跟着改。

查询层模式技能就是把这层胶水标准化:它让 AI 助手在生成代码时,自动遵循「UI → 查询层 → 服务层」的固定架构,产出带缓存键、带错误转换、带依赖注入的查询与突变。

一键安装 AI 技能提升开发效率的思路

所谓「一键安装」,本质是把一套团队约定好的架构规范,变成 AI 助手随时可调用的上下文,从此不用每次在对话里重复贴几百行范式代码。

安装前提(通用流程,具体以官方说明为准):

  1. 你使用的 AI 编程工具需支持「技能 / Skill」机制,具备技能市场或本地技能目录的加载能力;
  2. 本地项目已具备可写入的技能目录,且你对项目有写权限;
  3. 若是团队协作,建议统一技能版本(本技能当前为 1.1),避免成员之间范式不一致。

安装步骤:

  1. 在技能市场或技能管理面板中,按技能 ID 11868 检索「查询层模式 / query-layer」;
  2. 点击安装或启用,确认技能被写入本地技能目录;
  3. 在项目对话中确认技能已被识别——通常表现为 AI 助手能主动引用该技能的规则(如「错误必须在查询层边界转换」);
  4. 重启或刷新 AI 助手会话,使技能上下文生效。

验证是否生效:向 AI 助手提一个贴近真实需求的问题,例如「帮我写一个消费 services.db.recordings.getAll 的查询,用 TanStack Query,错误要转成用户可显示的类型」。如果回答中出现了 defineQuery、Err({ title, description, action })、.options 等该技能特有的范式,而不是泛泛的 useQuery 示例,说明技能已成功加载。若未生效,请检查技能是否启用、是否被更高优先级的规则覆盖,具体以官方说明为准。


该技能的主要功能和特点

  • 标准化的查询层架构:明确「UI 组件 → 查询层(RPC/查询) → 服务层(纯函数)」三层职责,查询层负责调用注入配置后的服务、转换错误、管理缓存、提供双接口。
  • 三层错误流转换:服务层的 TaggedError<'名称'>(领域特定)→ 查询层的 UserFacingError(展示就绪)→ UI 层的吐司通知,边界清晰,杜绝「组件里 catch 完再猜错误含义」。
  • 反模式防护:专门点名「双重包装错误」这一高频踩坑点,要求「转换一次,直接使用」,并给出 onError: (error) => notify.error.execute(error) 的正确写法。
  • 运行时依赖注入:查询层依据 settings.value[...] 动态选择服务实现,示例覆盖转录服务在 OpenAI / Groq 之间切换、录制器在 navigator / ffmpeg / cpal 之间切换(含 Tauri 环境判断 window.__TAURI_INTERNALS__)。
  • 双重接口模式:每个查询/突变同时暴露反应式 .options(静态对象,配合 createQuery / createMutation 使用)与命令式 .execute() / .fetch()(事件处理器与顺序工作流使用),并给出「何时用哪种」的对照表。
  • 缓存管理范式:乐观更新(先 setQueryData 立即刷新 UI,再 invalidateQueries 后台拉新)+ 层级化查询键(['db','recordings']、['db','recordings','latest']、['db','recordings', id])便于精准失效。
  • 参数化与初始数据:支持 getById: (id: Accessor<string>) => defineQuery({...}) 的访问器写法,以及基于已有缓存生成 initialData 与 initialDataUpdatedAt,避免二次加载闪烁。
  • 统一 RPC 命名空间:所有模块(db、recorder、transcription、clipboard、sound、analytics、notify)统一挂在 rpc 下,调用方只需 import { rpc } from '$lib/query'。
  • 多服务协同示例:notify 模块演示一次突变里同时触发吐司与操作系统通知,加载态只走吐司以避免通知轰炸。

和普通工具比强在哪:直接问 AI「怎么写 useQuery」,你得到的是通用示例;启用本技能后,你得到的是符合三层架构、错误已转换、缓存键已分层、还能随时切换服务实现的项目内代码,可读性和可维护性差异巨大。


如何使用该技能?

步骤一:明确你处在哪一层

先判断需求属于哪一类,技能对每类都有对应范式:

  1. 创建消费服务的查询或突变;
  2. 把服务层错误转成 UI 错误类型;
  3. 基于用户设置做运行时服务选择;
  4. 为即时反馈加乐观缓存更新;
  5. 理解双重接口(反应式与命令式)如何取舍。

步骤二:定义查询键(先分层,后写查询)

export const dbKeys = {
  recordings: {
    all: ['db', 'recordings'] as const,
    latest: ['db', 'recordings', 'latest'] as const,
    byId: (id: string) => ['db', 'recordings', id] as const,
  },
};

输入:模块名 + 资源名 + 可选参数。输出:可直接传给 invalidateQueries 的层级键,支持「整体失效」与「单条失效」两种粒度。

步骤三:写基础查询

export const db = {
  recordings: {
    getAll: defineQuery({
      queryKey: dbKeys.recordings.all,
      queryFn: () => services.db.recordings.getAll(),
    }),
  },
};

步骤四:写带错误转换的查询

enumerateDevices: defineQuery({
  queryKey: recorderKeys.devices,
  queryFn: async () => {
    const { data, error } = await recorderService().enumerateDevices();
    if (error) {
      return Err({
        title: '❌ 枚举设备失败',
        description: error.message,
        action: { type: 'more-details', error },
      });
    }
    return Ok(data);
  },
}),

输入:纯函数式服务调用返回的 Result。输出:Ok(data) 或已转为展示就绪的 Err(UserFacingError),UI 层无需再判断错误类型。

步骤五:在组件中用反应式接口

<script lang="ts">
  import { createQuery } from '@tanstack/svelte-query';
  import { rpc } from '$lib/query';

  const recordings = createQuery(() => rpc.db.recordings.getAll.options);
</script>

{#if recordings.isPending}
  <Spinner />
{:else if recordings.error}
  <Error message={recordings.error.description} />
{:else}
  {#each recordings.data as recording}
    <RecordingCard {recording} />
  {/each}
{/if}

注意:.options 是静态对象、不带括号,必须包在访问器函数里传入,这是本技能反复强调的关键规则。

步骤六:在事件处理器与工作流中用命令式接口

async function handleBulkDelete(recordings: Recording[]) {
  for (const recording of recordings) {
    const { error } = await rpc.db.recordings.delete.execute(recording);
    if (error) {
      notify.error.execute(error);
      return;
    }
  }
  notify.success.execute({ title: '所有录制删除成功' });
}

步骤七:需要即时反馈时加乐观更新

在突变成功后立刻 queryClient.setQueryData 写入新数据,再 invalidateQueries 让后台重新拉取;配合 onSettled 钩子失效相关状态键,保证缓存与服务器最终一致。

步骤八:接入统一命名空间

把各模块挂进 rpc:

export const rpc = { db, recorder, transcription, clipboard, sound, analytics, notify } as const;

至此,应用任何位置都能「一处导入、两种用法」。


同类型竞品对比分析

方案名称 定位 核心功能 安装/使用门槛 适用场景 优势与不足
查询层模式(query-layer,本技能) AI 技能:架构范式 + 代码生成规范 三层错误流、双重接口、运行时依赖注入、乐观更新、层级查询键、RPC 命名空间 需支持技能机制的 AI 工具;团队需理解 Result 与访问器写法 Svelte + TanStack Query 的分层项目、多服务商切换场景 优势:范式统一、错误边界清晰、可照抄落地;不足:强绑定 TanStack Query 与 WellCrafted 风格,学习曲线偏陡
TanStack Query 原生手写 数据请求库本身 缓存、重试、失效、乐观更新等底层能力 门槛低,但规范全靠团队自觉 任意 React / Svelte / Vue 项目 优势:灵活、生态成熟;不足:无错误转换与依赖注入约定,易演化成「每个组件一套写法」
SWR + 自建错误映射 轻量数据请求库方案 请求去重、重新验证、缓存 门槛低 中小型项目、页面级数据获取 优势:体积小、API 简单;不足:突变编排与乐观更新能力弱,跨层错误规范化仍需自建
RTK Query Redux 生态的数据层方案 端点定义、自动缓存、代码生成 需引入 Redux 体系,门槛中等偏高 已在用 Redux 的大型应用 优势:与状态管理深度整合;不足:与 Svelte 生态契合度低,样板代码较多
自研 fetch 封装 项目内私有工具 请求拦截、统一错误码 起步门槛最低 早期项目、需求简单 优势:完全可控;不足:缓存/反应性/乐观更新全部自建,长期维护成本高

选型建议:若项目已在 TanStack Query 上,且存在「服务层错误直接漏到 UI」「服务商可切换」的痛点,本技能的收益最直接;若只是页面级取数,原生 TanStack Query 或 SWR 已足够。


该技能的典型应用场景与实际体验情况

场景一:转录服务多服务商切换(后端开发者 / 全栈工程师)

痛点:产品支持 OpenAI 与 Groq 两家转录服务,用户在设置里一改,散落在各处的调用点全要改。

使用方式:按技能的「服务选择模式」,在查询层写一个 transcribeBlob,用 switch 读取 settings.value['transcription.selectedTranscriptionService'],每个分支带入对应 API Key、模型名、输出语言、提示词与温度参数;未选择时返回 Err({ title: '⚠️ 未选择转录服务', ... })。

实际体验:好处是「配置差异全部收口在一处」,新增服务商只是加一个 case;注意点是各服务商参数并非完全对称,需逐项核对设置键名,否则容易出现参数错位。

场景二:桌面端与浏览器端录制器选择(桌面应用开发者)

痛点:同一份前端代码要跑在浏览器与 Tauri 桌面端,录音实现不同(navigator / ffmpeg / cpal)。

使用方式:recorderService() 内先判断 window.__TAURI_INTERNALS__,浏览器直接返回 services.navigatorRecorder;桌面端用 recorderMap 按 settings.value['recording.method'] 取对应实现。

实际体验:这是该技能里最「开箱即用」的一段,几乎可以原样照搬;注意点在于运行环境判断要放在最前面,避免在无 Tauri 环境下访问桌面服务导致报错。

场景三:录音列表的乐观更新(前端工程师)

痛点:新建一条录音后要等后端返回、再重新拉列表,界面明显卡顿。

使用方式:在 create 突变的 mutationFn 内,服务调用成功后立刻 queryClient.setQueryData 把新记录插入 recordings.all 与 byId 缓存,再 invalidateQueries 触发后台刷新。

实际体验:交互体感提升最明显,「点完立即出现」;注意点是乐观写入的数据必须足够完整(含 id 与时间戳),否则后续按时间排序的 getLatest 会取到异常值。

场景四:批量删除与顺序工作流(业务开发)

痛点:批量删除时逐条调用,中途失败需要中断并提示;停止录制后要继续转录,属于典型串行流程。

使用方式:在事件处理器里用 .execute() 逐条调用,遇错即 notify.error.execute(error) 并 return;顺序工作流中先 .execute() 停止录制拿到 blob,失败则直接返回。

实际体验:.execute() 与 .options 的分工一旦形成肌肉记忆,代码可读性提升显著;注意点是 .execute() 不要用在组件渲染上下文里,createMutation 需要组件上下文,这条规则在技能中被明确列出。


该技能能为用户带来的价值

  • 统一范式,降低协作成本:错误转换、缓存键、接口形态都有唯一答案,新人接手时不需要「读懂十几种写法」,代码评审也可以直接对着规则清单走。
  • 把错误处理前置到边界:三层错误流让领域错误不会污染 UI 层,组件里只关心 error.description 怎么显示,减少大量防御性判断代码。
  • 切换服务商的边际成本接近于零:依赖注入把「改设置」和「改代码」解耦,新增一个服务商通常只需在查询层加一个分支。
  • 交互体感提升:乐观更新让列表类操作「点完即现」,配合后台失效刷新,兼顾速度与一致性。
  • 可量化的收益方向:新增一个查询/突变的代码量从「重复样板」压缩为「填空式定义」;错误提示的一致化减少了「同一个失败在不同页面有不同文案」的问题(具体提升幅度依团队规模与项目复杂度而定)。
  • 长期价值:查询层是少有能「越用越省」的架构投资——它同时沉淀了规范、示例与反模式,配合 AI 助手使用时,规范不再依赖口口相传。

常见问题 FAQ

Q1:这个技能怎么安装?需要付费吗?

A:在支持技能机制的 AI 编程工具中,通过技能市场或技能管理面板检索技能 ID 11868(或名称「查询层模式 / query-layer」)并启用即可,具体入口与是否收费以官方说明为准。安装后建议用第 1 部分给出的验证问题测试是否生效。

Q2:我只用 React,不用 Svelte,这个技能还有用吗?

A:技能的核心内容——三层错误流、运行时依赖注入、查询键层级化、乐观更新、禁止双重包装错误——是框架无关的架构思想。但示例代码大量使用 createQuery(() => ...options) 这类 Svelte 写法,React 用户需要自行替换为对应的 hooks 调用方式,技能不会自动帮你完成这层映射。

Q3:为什么一定要在查询层转换错误,直接在组件里 catch 不行吗?

A:技能明确要求「始终在查询边界转换错误,切勿返回原始服务错误」。原因是服务层抛的是领域特定错误(TaggedError<'名称'>),组件关心的是「标题写什么、描述写什么、要不要给「查看更多」按钮」。若在组件里转换,同一类错误会在不同组件里被写出不同文案,且容易触发「双重包装」反模式。

Q4:.options 为什么不能加括号?

A:.options 是静态对象,不是函数。在 Svelte 中需包在访问器函数里传入,如 createQuery(() => rpc.db.recordings.getAll.options);写成 options() 会直接报错。这是技能「关键规则」中特别强调的一条。

Q5:它和直接用 TanStack Query 官方文档学有什么区别?

A:官方文档教你「库怎么用」,本技能教你「在分层架构里怎么用才不出乱子」。前者给你 useQuery 的 API,后者给你错误边界、依赖注入、RPC 命名空间、查询键分层等一套团队级约定,并明确指出哪些写法是反模式。若你的项目只有零星几个请求,官方文档足够;若项目要长期演进并多人协作,本技能的范式价值更明显。