[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 移除quickFilterToFiltersinline 定义(已在 hook 内复用)saveParams大对象 — 改成buildMutateParams(selectKeys, selectTotal, selectOption)
替换¶
state.tableData→dataSource.tableDatastate.total→dataSource.totalstate.truncated→dataSource.truncatedstate.scanLimit→dataSource.scanLimitstate.allCount→dataSource.allCountdataLoading→dataSource.loadingdataRefresh()→dataSource.refresh()saveParams→dataSource.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 filterqualityConfigItem.watch仍在 LocalDataView 顶层(与 mode 联动)— client mode bump qualityVersion,server mode 调dataSource.refresh()merchantStatsRun/countRun仍在 LocalDataView(这俩是独立数据源,不属于"主表 data"范畴)selectKeysreset 通过 hook 的onServerSuccesscallback 由 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 留独立做"