SuperAPI AI 工具集

data-fetching

编程与开发

查看项目

更新于 10-15 09:11

技能介绍

用途

本技能是 LobeHub 的数据获取架构指南,规定了在项目中实现数据获取、创建服务、使用 store hooks 时应遵循的分层模式。其目标是统一 API 调用入口、避免在组件中直接处理服务端数据、并通过 SWR 管理缓存与失效。

架构概览

整体分为三层,数据自上而下流动:

  1. 组件(Component):调用 store 中的 useFetchXxx hook。
  2. Zustand Store(状态 + Hook):通过 useClientDataSWR 调用 service。
  3. Service 层(xxxService):调用 lambdaClient(TRPC Client)。

核心原则

应做(DO):

  • 所有 API 调用使用 Service 层。
  • 数据获取使用 Store 的 SWR Hooks,而非 useEffect。
  • 使用恰当的数据结构(List 与 Detail 模式参见 store-data-structures 技能)。
  • 写操作(创建/更新/删除)使用 lambdaClient.mutate。
  • 仅在 service 方法内部使用 lambdaClient.query。
  • 命名约定:读取 hooks 为 useFetchXxx,缓存失效辅助函数为 refreshXxx(例如 useFetchBenchmarks / refreshBenchmarks)。变更操作在 service 调用后链式调用 refreshXxx()。

不应做(DON'T):

  • 绝不使用 useEffect 进行数据获取。
  • 绝不在组件或 store 中直接调用 lambdaClient。
  • 绝不用 useState 存放服务端数据。
  • 绝不混用数据结构模式,需遵循 store-data-structures 技能。

第一层:Service 层

目的:封装所有对 lambdaClient 的 API 调用;提供清晰、带类型的接口;作为 API 操作的唯一来源。

结构示例(src/services/agentEval.ts):定义 AgentEvalService 类,包含查询方法(listBenchmarks、getBenchmark)与变更方法(createBenchmark、updateBenchmark、deleteBenchmark),并导出单例 agentEvalService。

指南:每个领域一个 service(如 agentEval、ragEval、aiAgent);导出单例实例;方法名与操作对应(list、get、create、update、delete);参数类型清晰(复杂参数使用接口)。

第二层:带 SWR Hooks 的 Store

目的:管理客户端状态;提供用于数据获取的 SWR hooks;处理缓存失效。

状态结构(initialState.ts):列表数据用简单数组(benchmarkList、benchmarkListInit);详情数据用 map 缓存(benchmarkDetailMap、loadingBenchmarkDetailIds);变更状态(isCreatingBenchmark、isUpdatingBenchmark、isDeletingBenchmark)。

Actions(action.ts):定义 SWR key 常量;useFetchBenchmarks、useFetchBenchmarkDetail 为数据获取 hooks;refreshBenchmarks、refreshBenchmarkDetail 为缓存失效方法;createBenchmark、updateBenchmark、deleteBenchmark 为变更操作;internal_dispatchBenchmarkDetail、internal_updateBenchmarkDetailLoading 为内部方法(不供 UI 直接使用)。

Store 指南:SWR key 作为常量置于文件顶部;所有数据获取使用 useClientDataSWR(绝不用 useEffect);onSuccess 回调更新 store 状态;refresh 方法使用 mutate() 使缓存失效;加载状态放在 initialState 中并在 onSuccess 更新;变更操作先调用 service,再刷新相关缓存。

第三层:组件用法

获取列表数据:从 store 取 useFetchBenchmarks hook、benchmarkList 数据与 benchmarkListInit 标志,调用 hook,根据 isInit 渲染加载态或列表。

获取详情数据:从路由取 benchmarkId,取 useFetchBenchmarkDetail hook,从 benchmarkDetailMap 读取详情,从 loadingBenchmarkDetailIds 判断单项加载态,调用 hook 后渲染。

推荐使用 Selectors:在 selectors.ts 中定义 benchmarkSelectors(如 getBenchmarkDetail、isLoadingBenchmarkDetail),组件通过 selector 读取。

反模式:使用 useEffect + useState 直接调用 lambdaClient 获取数据是错误的。

组件中的变更操作:创建操作使用全局 isCreatingBenchmark 标志驱动表单加载;更新/删除使用按项加载状态(loadingXxxIds),避免冻结无关行。原因:创建时尚无 id,单一标志足够;更新/删除针对特定行,全局标志会影响其他行。

常见模式

模式 1:分页。缓存 key 数组必须包含所有应触发重新获取的参数(如 datasetId、limit、offset)。

模式 2:依赖获取。两个 hook 并行运行,SWR 去重,无需手动排序。

模式 3:条件获取。传入 undefined 可完全禁用 hook(如仅在弹窗打开且 id 存在时获取)。

模式 4:跨域刷新。删除操作后除刷新自身缓存外,还可刷新相关缓存(如 refreshDatasets)。

迁移指南:useEffect → Store SWR

迁移分三步:1. 添加 service 方法;2. 添加 store slice hook;3. 组件改为从 store 读取。文档给出了迁移前后的完整代码对比。

故障排查

  • 数据始终不加载:检查 hook 是否被调用、key 是否为 null/undefined、网络面板是否有请求。
  • 变更后数据陈旧:检查 refreshXxx 是否运行、缓存 key 是否与 hook 使用的一致。
  • 加载状态卡在 true:检查 onSuccess 是否写入 loading=false、Promise 是否静默拒绝。
  • 详情 map 缺少条目:检查 reducer dispatch 是否运行、isEqual 是否因陈旧数据短路。

总结清单

新增数据获取时:

  • 步骤 1(类型与状态):在 @lobechat/types 定义类型(Detail 类型 + List item 类型);状态结构为 xxxList、xxxDetailMap、loadingXxxDetailIds;如需乐观更新则添加 reducer。
  • 步骤 2(Service 层):在 src/services/xxxService.ts 创建 service,方法包括 listXxx()、getXxx(id)、createXxx()、updateXxx()、deleteXxx()。
  • 步骤 3(Store Actions):initialState.ts 定义状态结构;action.ts 包含 useFetchXxxList()、useFetchXxxDetail(id) 等 SWR hooks 与 refreshXxxList()、refreshXxxDetail(id) 等缓存失效方法。

适用场景与限制

适用场景:在 LobeHub 项目中实现数据获取、创建 service、编写 store hooks、进行变更操作与缓存失效、从 useEffect 迁移到 Store SWR 模式。

限制:本技能针对 LobeHub 特定的三层架构(Service + Zustand Store + SWR + lambdaClient/TRPC),依赖 useClientDataSWR、lambdaClient 等内部工具;不适用于其他技术栈。文档中未提供具体安装命令、收费信息或平台支持范围。