在 Vue 项目开发中,我们经常需要为表单提供默认值。在一个 Vue3 + TypeScript 项目中,我就因为处理默认表单数据的方式不当,踩了一个典型的坑:表单数据意外复用,导致新增页面出现上一次的输入值。
这次经历让我更深刻地理解了 Vue 2 中 data
要求必须是函数返回对象的设计初衷,也让我意识到即使在 Vue 3 中,这类问题依然需要我们主动规避。
问题起因:导出默认表单数据对象
我在项目中封装了一个表单的默认数据,代码如下:
js
// defaultFormData.ts
export const defaultFormData = {
name: '',
age: 0,
gender: ''
};
在新增页面中,初始化表单数据时这样写:
js
import { defaultFormData } from './defaultFormData';
const formData = reactive(defaultFormData);
刚开始一切正常,页面渲染、填写、提交都没问题。但问题出现在我点击"新增"跳转页面第二次创建数据的时候。
Bug 现象:表单初始化后带着上次填写的值
当我点击"新增"进入页面时,发现表单字段中竟然自动带上了上一次输入的值。
我明明没有使用缓存、没有从接口回填、也没有做本地存储,为什么页面初始化时就带上了旧数据?
这时我意识到:我是不是复用了某个不该复用的东西?
问题根因:引用共享导致状态污染
答案是肯定的。
当我写下:
js
const formData = reactive(defaultFormData);
其实是将 defaultFormData
这个对象直接传入 reactive
,而 reactive
本身是引用式响应。
也就是说:
- 第一次新增页面中
formData
修改了数据,其实就直接修改了defaultFormData
本身。 - 当我第二次使用
defaultFormData
初始化表单时,它已经不是最初的"默认值",而是"被污染"的旧数据。
这个问题本质上就是:共享了同一个引用对象,修改了一处,影响了所有引用它的地方。
解决方案:每次都生成新的初始对象
既然问题出在共享引用,那我们就需要确保每次都获得新的对象。
有几种解决方式:
✅ 推荐方式:导出一个返回新对象的函数
js
// defaultFormData.ts
export function getDefaultFormData() {
return {
name: '',
age: 0,
gender: ''
};
}
在页面中使用时:
js
const formData = reactive(getDefaultFormData());
每次调用函数都会创建一个全新的对象,完全避免引用共享问题。
其他方式(不推荐,但了解一下)
- 使用浅拷贝:
reactive({ ...defaultFormData })
- 使用深拷贝(如 lodash 的
cloneDeep
或JSON.parse(JSON.stringify(...))
)
虽然也能解决问题,但语义性、可维护性、性能都不如使用函数。
联想到 Vue 2 中的经典问题:为何 data
必须是函数?
这次让我突然明白了 Vue 2 中的这道"八股文"题背后的真实含义:
Vue 2 要求组件的
data
是函数,目的是为了保证每个组件实例返回一个新的数据对象,防止不同组件实例之间共享引用,导致数据污染。
通过这次真实的踩坑,我"理解"了这个规则背后的必要性。
总结:引用类型的默认值要特别小心
这次经历告诉我:在 Vue 项目中使用默认数据时,如果这个数据是引用类型(对象、数组),一定要小心共享引用的问题。
✅ 最安全的方式:导出一个函数,每次调用都返回新的对象。
不仅避免了数据污染,也让代码的语义更清晰、逻辑更独立。
这也是我认为,Vue 中关于 data
函数化的设计,不是八股,而是真实开发中你迟早会理解的一条"黄金准则"。