2026-04-16-19-28-04 - 彻底重构自动保存机制,修复路由切换后所有内容丢失问题

This commit is contained in:
2026-04-16 19:56:23 +08:00
parent 22d3ce0e35
commit 39ecdf2b71
5 changed files with 298 additions and 20 deletions

View File

@@ -152,3 +152,29 @@
- 在无法使用 Docker 的环境中,可将 `npm run build && npm run preview` 作为标准部署脚本;
- 重新部署前务必先清理旧的同类型进程,避免端口冲突或多版本服务同时运行导致访问混乱;
- 如需固定端口,可在 `package.json``preview` 脚本中增加 `--port` 参数(如 `vite preview --port 8080`)。
---
## 记录 8路由切换后所有内容仍然丢失——彻底重构自动保存机制
**A. 具体问题**
`/report-editor` 中编辑报告(填写基本信息、上传视频、自动/手动截取关键帧、拖拽图片到 placeholder切换到 `/report-manage` 再返回 `/report-editor`,报告编辑器内容、基本信息、视频列表、关键帧截图**全部丢失**。
**B. 产生问题原因**
1. 自动保存机制过度依赖 `stateRef``contentRef` 作为"数据快照"。
2. **React 18 `StrictMode`** 在开发/预览环境下会执行"挂载 → 立即卸载 → 重新挂载"。在首次模拟卸载时,`stateRef.current` 仍然是组件创建时的初始空值(`videos: []``capturedFrames: []`、默认 `reportData`)。
3. 组件卸载cleanup时调用保存用这个空值**覆盖了 localStorage 中已有的正确 draft**。
4. 重新挂载后,系统读取了被清空的 draft导致所有数据全部丢失。
5. 此前两次修复仅把 `stateRef.current` 同步移到了更多恢复分支中,但**没有从根本上消除对 ref 的依赖**,因此 `StrictMode` 下的首次卸载仍会覆盖有效 draft。
**C. 解决问题方案**
1. **彻底重构 `saveDraftToStorage`**:不再读取 `contentRef.current``stateRef.current`,而是直接从最新的 React state 和 `editorRef.current?.innerHTML` 获取数据。`useCallback` 的 dependency 数组包含 `reportData``videos``capturedFrames``activeTab``loadedTemplateId``reportId`,确保闭包永远绑定当前渲染周期的最新 state。
2. **重构自动保存 effect**:将 `beforeunload``visibilitychange` 事件处理器直接绑定到 `saveDraftToStorage`effect 的 dependency 改为 `[saveDraftToStorage]`。这样即使 `StrictMode` 导致组件在首次挂载后立即卸载cleanup 中调用的 `saveDraftToStorage` 也指向最新数据的闭包,不会用空值覆盖已有 draft。
3. **给 `useLayoutEffect` 安全网添加 `[]` 依赖**:防止每次渲染后重复执行,避免潜在的意外覆盖。
**D. 后续如何避免问题**
- **永远不要将 `useRef` 作为自动保存的唯一数据源**。ref 在 React 18 `StrictMode` 的模拟卸载阶段仍然保持初始值,会导致用空数据覆盖有效持久化数据。
- 自动保存函数应直接从最新的 React state 和 DOM 读取数据,通过 `useCallback` + 完整的 dependency 数组保证闭包始终新鲜。
- 在开发阶段应始终开启 `StrictMode` 测试,因为它能暴露 ref-based 状态同步在卸载/重挂载时的隐藏 bug。
- 对于大型表单/编辑器组件,应将自动保存逻辑与业务状态彻底解耦,统一通过 hook 的最新状态闭包来持久化。