跳转至

[ISSUE-0059] useDataSource hook 大重构

ISSUE-0054 第 10 轮 agent 元洞察:

"把 saveParams / dataRefresh / allCount 这些跨路径状态抽成单一信号源(如 useDataSource hook 暴露 data, total, refresh, mutate),让 mode 切换只换 hook 实现。否则未来还会出 ISSUE-0055/0056(删错、刷不到、CTA 不见)。"

ISSUE-0054 ~ 0058 走的都是补丁式修法(每加个 client mode 就在每条路径加 if (mode === 'client'))。本轮做 agent 一直推荐的治本重构。

设计

新 hook:src/hooks/use-data-source.ts

输入参数(17 个 — UI state 全透传):

{
  mode, rowsSnapshot, filterTaskId, quickFilter, keyword, filters, logic,
  sortModel, page, pageSize, qualityVersion, optimisticDeletedIds, viewId,
  onRequestRefreshSnapshot, onServerSuccess
}

返回值(8 个 — 统一接口):

{
  tableData,        // mode-aware 表格行
  total,            // mode-aware 总数
  loading,          // 仅 server mode 拉数据时
  truncated,        // server hasEmailOnly path 50k cap 信号
  scanLimit,        // truncated banner 文案
  allCount,         // 空状态 CTA 用(仅 viewId='all'+无 filter+无 keyword 时填充)
  refresh,          // mode-aware:client → snapshot;server → dataRefresh
  buildMutateParams // 统一构造 delete/export saveParams(含 quickFilter merge + taskId + hasEmailOnly)
}

Hook 内部职责: 1. client mode: useMemo 派生(taskId+quickFilter+keyword+sort+paginate) 2. server mode: useRequest(apiLocalDataList) + onSuccess setState + useEffect 触发 dataRun 3. mode-aware refresh: client → onRequestRefreshSnapshot();server → dataRefresh() 4. mutate params builder: 统一构造(quickFilter merge + taskId + hasEmailOnly + computed sort)

LocalDataView 改造

删除(约 -120 行)

  • clientPaged = useMemo(...) 整段
  • useRequest(apiLocalDataList, ...) + onSuccess setState 链
  • getTableData() 函数
  • 两个 server-mode useEffect 触发 dataRun
  • state.tableData / state.total / state.truncated / state.scanLimit / state.allCount 从 useSetState 移除
  • quickFilterToFilters inline 定义(已在 hook 内复用)
  • saveParams 大对象 — 改成 buildMutateParams(selectKeys, selectTotal, selectOption)

替换

  • state.tableDatadataSource.tableData
  • state.totaldataSource.total
  • state.truncateddataSource.truncated
  • state.scanLimitdataSource.scanLimit
  • state.allCountdataSource.allCount
  • dataLoadingdataSource.loading
  • dataRefresh()dataSource.refresh()
  • saveParamsdataSource.buildMutateParams(selectKeys, selectTotal, selectOption)

行数变化

文件 之前 之后 变化
local-data-view.tsx 1050 930 -120
hooks/use-data-source.ts 0 262 +262
净加 +142

虽然总行数 +142,但关注分离: - LocalDataView 只剩 UI / state machine / event handler — 容易理解 - useDataSource 是纯 data layer — 可独立测试 + 复用

收益(vs 旧补丁式)

维度 旧补丁式(v0.10.78) useDataSource(v0.10.79)
mode 切换的代码量 每条路径 if (mode === 'client') 各一份 hook 内部统一处理
新加 quickFilter 改 quickFilterToFilters(外部) + clientPaged + getTableData + saveParams 多处 仅改 hook 内部 quickFilterToFilters 一处
新加路径(如 deleteByTaskId) 自己处理 mode-aware 直接调 dataSource.refresh
测试粒度 LocalDataView 整体测 1050 行 hook 独立测 + 组件独立测
未来扩展(e.g. mode='hybrid') 改 5+ 处 改 hook 内部 mode switch 一处

验证

  • ✅ TypeScript compile 0 error
  • ✅ pnpm build 通过
  • ✅ scan:error-handling --diff 0 新增
  • ⚠️ Runtime 需用户实测(client mode 派生 / server mode 拉取 / mode 切换 / delete/export / refresh)

注意点

  • optimisticDeletedIds 仍由 LocalDataView 管理(含 onBefore/onError 维护 + rowsSnapshot 监听清空),但传给 hook 用于 clientPaged filter
  • qualityConfigItem.watch 仍在 LocalDataView 顶层(与 mode 联动)— client mode bump qualityVersion,server mode 调 dataSource.refresh()
  • merchantStatsRun / countRun 仍在 LocalDataView(这俩是独立数据源,不属于"主表 data"范畴)
  • selectKeys reset 通过 hook 的 onServerSuccess callback 由 LocalDataView setState

audit_grep

- pattern: "useRequest\\(apiLocalDataList"
  description: "apiLocalDataList useRequest 只能在 useDataSource hook 内部"

防未来有人再在 LocalDataView 加 dataRun → 走回头路。

元洞察

Agent 一直推荐"useDataSource hook",但 v0.10.74-78 都走补丁式。 本轮做完才理解:补丁式有"渐进可控"优点,但架构债务每轮 ISSUE 都涨。 重构有"短期风险",但长期心智负担降到 1/n

平衡点:补丁式做到第 3-5 个补丁后,必须停下来重构。否则补丁的 surface area 越来越大,再修 bug 越来越难。本轮就是这个 inflection point。

相关

  • [[0054-round10-agent-a2-6-bugs|0054-第10轮agent-A2-6处真bug]] — agent 元洞察来源
  • [[0056-round11-agent-v0.10.75-4-bugs|0056-第11轮agent-v0.10.75-4个新bug]] — 补丁式漏 4 个 quickFilter(架构问题症状)
  • [[0058-improvements-task-limit-delete-keyword|0058-长期改进3连击-task-limit-optimistic-delete-keyword]] — 上一轮补丁式 + 列出 "useDataSource 留独立做"