mcpField
D
data-fetching-architecture

data-fetching-architecture

github.com/lobehub开发83.6k57 次安装今天更新
代码仓库

关于 data-fetching-architecture

面向 LobeHub 开发者的数据获取架构技能,说明如何实现或迁移应用中的数据请求。内容涵盖服务层、Store、SWR、缓存、变更操作及界面状态约定。

主要功能

  1. 01服务层与 Store 的职责边界
  2. 02SWR Hook 与缓存键约定
  3. 03变更操作和乐观更新指南
  4. 04加载、错误与空状态处理
  5. 05现有请求迁移清单

适用场景

  1. 01添加由 Store 管理的读取 Hook
  2. 02写入后刷新相关缓存
  3. 03处理列表请求的失败与重试
  4. 04将组件级请求迁入服务层和 Store

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/lobehub/lobehub --skill data-fetching-architecture

安装后在 Claude Code 中输入/data-fetching-architecture确认已加载。

附带文件references/home-first-paint.mdreferences/walkthrough.md

详细介绍

LobeHub 数据获取架构

Component → Store useFetchXxx hook → Service → lambdaClient
          ← SWR request state + store data ← response

层级边界

  • src/services/ 中的服务负责 API 调用:读取使用 .query(),写入使用 .mutate()。 每个领域导出一个服务实例;组件和 store 不直接调用 lambdaClient。
  • Store 读取钩子使用 useClientDataSWR,并返回其 SWR 响应,包括 error 和 mutate。通过封装器支持的回调(onSuccess,或同步封装器中的 onData), 将成功结果同步到 store。
  • 从本地副本渲染的服务端列表和详情应改用 @/libs/replica: store 的 useFetchXxx 返回 slice.useSync(params)(仅标志位),由引擎写入 store。 请参阅 zustand 中的 Replica 部分。
  • 组件调用这些钩子,并通过选择器读取 store 数据。不要在 useEffect 中获取数据,也不要在组件的 useState 中重复存储服务端数据。
  • 读取钩子使用 useFetchXxx,缓存失效使用 refreshXxx。
  • 列表/详情类型、映射表、reducer 和共享类型来源请参阅 Zustand 数据结构。有关 store 布局 和 action 类,请参阅 zustand。

首页首次绘制与持久化缓存

更改首页/侧边栏的首次绘制或持久化显示数据时,请阅读 references/home-first-paint.md。其中介绍了如何 避免闪烁、复用持久化数据,以及用户 displaySnapshot.ts 边界。

缓存键与刷新

复用 src/libs/swr/keys.ts 中的领域键工厂;如果该领域没有键,则定义一个共享键。 包含所有会影响响应的参数:实体或父级 ID、筛选条件、排序和分页。读取和刷新必须 构造相同的键。

从 @/libs/swr 导入 useClientDataSWR 和 mutate,以保留应用的 workspace/缓存处理逻辑。缺少必需 ID 时返回 null 键;将 undefined 传给 store 钩子,只有当该钩子将其映射为 null 时才会禁用数据获取。

例如,在 action 类中:

useFetchBenchmarks = () =>
  useClientDataSWR(evalKeys.benchmarks(), () => agentEvalService.listBenchmarks(), {
    onSuccess: (data) => {
      this.#set({ benchmarkList: data, benchmarkListInit: true });
    },
  });

refreshBenchmarks = async () => {
  await mutate(evalKeys.benchmarks());
};

请查看当前的 benchmark action 了解 store 的接线方式,并查看 SWR 封装器 了解其 实际选项。这些链接用于定位实现,并不意味着所有现有调用点都可以原样照搬。

共享已知父级 ID 的钩子可以并行运行。如果某个请求需要另一个请求返回的值, 则在该值可用之前,将其键保持为 null。不要假设应用封装器使用上游 SWR 的默认 去重间隔。

对于按多个父级分别缓存的列表,请参阅 按父级键控的列表。普通的扁平列表无需额外一层。

变更

  • 调用服务,然后刷新受影响的列表/详情键,以及响应发生变化的任何相关领域。 如果后续 UI 行为依赖刷新结果,则应等待刷新完成。
  • 在 store 状态中保留待处理标志,并在 finally 中清除。行更新/删除使用按 ID 区分的状态,以免影响其他行的使用;创建操作可以使用单独的标志,因为此时还没有 持久化 ID。
  • 对 replica 数据进行乐观写入时,使用 slice.optimistic / entity.optimistic; 调用被拒绝时会自动回滚,包括删除操作。
  • 对旧版 store 执行乐观创建/更新时,使用 store 的 reducer/dispatch 约定;如果服务调用失败,则恢复或重新验证受影响的状态。调用被拒绝后,不要 留下临时行或看似成功的编辑。在这类 store 中,只有服务端成功后才能删除: 手写 reducer 没有回滚基准状态。
  • 让错误传递给调用方的错误 UI;finally 块会清除待处理状态,但本身不会恢复 乐观写入。

渲染加载状态、错误和已完成的数据

使用读取钩子的 SWR 响应。仅表示成功的标志(例如 isInit)、缺失的映射项以及 data ?? [],无法区分初始请求失败、加载中和空结果。

标准的加载、错误、空和数据界面请使用 AsyncBoundary;自定义布局和行内错误请使用 AsyncError。将原始 SWR data 传给边界:undefined 表示没有成功结果,而 [] 表示请求已完成且结果为空。

Replica useSync 不会返回 data。将 error 和来自 store 条目的已完成信号传给 AsyncBoundary(data={isListInit || undefined},其中选择器为 entry !== undefined);参见 src/features/Pages/PageLayout/Body/index.tsx。

const BenchmarkList = () => {
  const useFetchBenchmarks = useEvalStore((s) => s.useFetchBenchmarks);
  const benchmarks = useEvalStore((s) => s.benchmarkList);
  const { data, error, isLoading, mutate } = useFetchBenchmarks();

  return (
    <AsyncBoundary
      data={data}
      empty={<EmptyState />}
      error={error}
      isEmpty={data?.length === 0}
      isLoading={isLoading}
      onRetry={() => {
        void mutate();
      }}
    >
      <BenchmarkCards items={benchmarks} />
    </AsyncBoundary>
  );
};

示例中的 EmptyState 和 BenchmarkCards 代表该界面现有的渲染器。调整示例时请遵循以下区别:

  • 在空状态、NotFound 或零值默认内容之前显示首次加载错误。不要在 if (!map[id]) return <Skeleton /> 之后放置错误分支;失败时映射项可能永远不会填充。
  • 后台重新验证失败时保留已完成的内容,包括成功加载的空列表。重试进行中时应显示待处理反馈。
  • 对于由获取数据和静态数据组成的列表,请在合并行之前检查获取部分的请求状态。静态行不得掩盖失败的请求。
  • 对于无限滚动,请按桶保存 loadMoreError,并显示行内重试行。该错误尚未解决时,应暂停由观察器触发的重试。
  • 已关闭的模态框或不存在的 id 会禁用请求;这并不能证明记录不存在。

迁移现有的获取逻辑

将 API 调用移至其 service,并将请求移至 store SWR 钩子。除非任务需要更改,否则沿用现有的状态形状和 action 组织方式。用该钩子和选择器替换组件的 effect/本地获取状态,然后接入 error、重试和待处理反馈。写入后刷新相同的缓存键。

针对正在修改的界面,检查初始失败、重试、已完成的空结果、后台失败以及切换 id 的情况。对于过期数据,请比较读取/刷新键及其更新的 store 桶;对于卡在加载状态的情况,除成功回调外,也要检查被拒绝的请求。不要添加第二次获取或另一个加载标志来掩盖问题。

评论

支持 Markdown