React 本地草稿状态:自动保存之前,先定义脏数据边界

📅 2026/7/6 4:56:21
React 本地草稿状态:自动保存之前,先定义脏数据边界
React 本地草稿状态自动保存之前先定义脏数据边界一、自动保存看似温柔失败时最容易伤害用户独立产品里的编辑器通常都会加入自动保存。它减少用户点击也让产品显得更顺滑。但自动保存不是简单的onChange加接口调用。真正的问题是什么时候认为内容已经变脏什么时候可以覆盖远端什么时候必须阻止用户离开。如果边界不清楚自动保存会制造新的不安。用户刚输入半句话系统保存了一个不完整版本网络抖动时本地状态显示成功远端其实失败多标签页同时编辑时后打开的页面覆盖先写好的内容。小工具也会遇到这些问题只是规模更小不代表可以忽略。二、把编辑状态拆成本地、待提交和已确认一个稳定的草稿模型至少包含三层本地编辑态、待提交队列、远端已确认版本。UI 展示的是本地态但保存状态应该来自队列和确认版本。这样才能解释“正在保存”“保存失败”“有远端更新”等状态。stateDiagram-v2 [*] -- Clean Clean -- Dirty: 用户编辑 Dirty -- Saving: 防抖触发 Saving -- Clean: 服务端确认 Saving -- Failed: 超时或冲突 Failed -- Saving: 用户重试 Dirty -- Conflict: 远端版本变化 Conflict -- Dirty: 用户选择保留本地 Conflict -- Clean: 用户接受远端这里的关键是版本号。每次保存都应该带上baseVersion。服务端发现版本不匹配时不能静默覆盖而要返回冲突。冲突不一定需要复杂合并但必须可见。三、用队列避免保存请求互相踩踏React 组件里可以把保存逻辑封装为 hook但不要把请求散落在各个输入控件中。下面的示例保留一个简单队列只允许最后一次内容进入提交。失败时不会丢掉本地态。function useDraftAutosave(api: DraftApi, draftId: string) { const [status, setStatus] React.useStateclean | dirty | saving | failed(clean); const pendingRef React.useRefstring | null(null); const timerRef React.useRefnumber | null(null); const scheduleSave React.useCallback((content: string, version: number) { pendingRef.current content; setStatus(dirty); if (timerRef.current) window.clearTimeout(timerRef.current); timerRef.current window.setTimeout(async () { const snapshot pendingRef.current; if (snapshot null) return; setStatus(saving); try { await api.saveDraft({ draftId, content: snapshot, baseVersion: version, timeoutMs: 1200 }); if (pendingRef.current snapshot) setStatus(clean); } catch { setStatus(failed); } }, 600); }, [api, draftId]); return { status, scheduleSave }; }生产环境里还需要处理组件卸载、页面隐藏和离线恢复。hook 只负责局部交互持久化队列最好放到更上层必要时写入 IndexedDB。四、自动保存的边界是不要把所有变化都保存不是所有编辑都应该触发保存。光标位置、选区、折叠面板、临时筛选条件可以保存在本地不必进入服务端。否则服务端会承受大量低价值写入历史版本也会被无意义变化污染。还要控制保存频率。防抖可以减少请求但过长会增加丢失风险。一个常见策略是输入时 600 到 1000 毫秒防抖页面隐藏时立即保存失败后进入指数退避。不要无限重试尤其在移动网络下无限重试会耗电也会造成接口噪音。最后是用户信任。自动保存不应该只用一个小图标表达状态。失败时要给出明确动作重试、复制本地内容、查看冲突。克制的界面不等于沉默关键状态必须被看见。五、总结React 本地草稿状态的重点不是把保存做得无感而是让保存边界可靠。实现时要区分本地态、待提交队列和远端确认版本用版本号处理冲突用队列控制请求顺序。自动保存越安静失败状态越要清楚。用户可以少点一次按钮但不应该少一份确定性。