Vue 3 表单草稿自动保存需监听单个 ref 或组合字段、加防抖延迟写入、初始化时用 isHydrated 避免死循环、排除敏感字段并清洗复杂数据,确保轻量、安全、不干扰用户。

Vue 3 中用 watch 实现表单草稿自动保存,关键不是“一有变化就存”,而是“只在合适时机、存合适内容、不干扰用户”。核心是监听 + 防抖 + 安全序列化 + 初始化隔离,不依赖 Vuex,轻量可控。
按字段粒度监听,避免无效写入
不要直接监听整个表单对象(比如 reactive({ name: '', email: '' }))并加 deep: true——它会响应内部任意嵌套属性变更,包括未修改的字段,容易触发冗余存储。更推荐:
- 监听单个
ref:如watch(userName, saveDraft),适合独立输入框 - 监听多个字段组合:如
watch([userName, userEmail], ([n1, n2]) => { /* 合并保存 */ }) - 监听计算后的扁平结构:如
watch(() => ({ userName: userName.value, userEmail: userEmail.value }), saveDraft),确保只捕获业务关心的字段
必须加防抖,防止高频写入卡顿
用户每敲一个字都调用 localStorage.setItem,不仅浪费资源,还可能在低端设备上造成输入延迟。正确做法是延迟执行:
- 手写简易防抖函数:
const debounce = (fn, delay) => { let t; return () => { clearTimeout(t); t = setTimeout(fn, delay); }; }; - 包裹保存逻辑:
const saveDebounced = debounce(() => localStorage.setItem('draft', JSON.stringify(formData)), 600); - 在
watch回调中调用:watch(formData, () => saveDebounced(), { deep: true }); - 补充兜底:在
beforeunload事件中强制执行一次saveDebounced.flush()(若用 lodash)或直接调用保存,确保最后修改不丢失
页面加载时安全恢复,避开“假变更”循环
如果一上来就读缓存、直接赋值,watch 会立刻触发,导致刚恢复就又存一遍,形成死循环。需引入初始化标志:
立即学习“前端免费学习笔记(深入)”;
- 定义
const isHydrated = ref(false); - 在
onMounted中读取缓存:const cached = localStorage.getItem('draft'); - 成功解析后赋值:
if (cached) Object.assign(formData, JSON.parse(cached)); -
watch回调开头加判断:if (!isHydrated.value) return; - 赋值完成后,在
nextTick中设isHydrated.value = true; - 解析失败(如缓存结构过期)时,捕获异常并主动清除:
localStorage.removeItem('draft');
控制哪些数据能存,兼顾安全与体验
不是所有字段都适合自动暂存:
- 敏感字段(密码、身份证号、银行卡号)应从保存白名单中排除,不参与序列化
- 富文本、文件列表等复杂结构,先做清洗:过滤掉
undefined、函数、Date对象(JSON.stringify会忽略它们),或改用structuredClone(现代环境) - 提供“清空草稿”按钮,点击后调用
localStorage.removeItem('draft')并重置表单 - 路由离开前检查缓存是否与当前值不同,可弹提示:“检测到未提交的草稿,是否保留?”


















