一次表单数据复用引发的 Bug:理解 Vue 中的 data 为何是函数

在 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 的 cloneDeepJSON.parse(JSON.stringify(...))

虽然也能解决问题,但语义性、可维护性、性能都不如使用函数。


联想到 Vue 2 中的经典问题:为何 data 必须是函数?

这次让我突然明白了 Vue 2 中的这道"八股文"题背后的真实含义:

Vue 2 要求组件的 data 是函数,目的是为了保证每个组件实例返回一个新的数据对象,防止不同组件实例之间共享引用,导致数据污染。

通过这次真实的踩坑,我"理解"了这个规则背后的必要性。


总结:引用类型的默认值要特别小心

这次经历告诉我:在 Vue 项目中使用默认数据时,如果这个数据是引用类型(对象、数组),一定要小心共享引用的问题。

✅ 最安全的方式:导出一个函数,每次调用都返回新的对象

不仅避免了数据污染,也让代码的语义更清晰、逻辑更独立。

这也是我认为,Vue 中关于 data 函数化的设计,不是八股,而是真实开发中你迟早会理解的一条"黄金准则"。

相关推荐
橙序员小站2 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
炫饭第一名4 小时前
速通Canvas指北🦮——基础入门篇
前端·javascript·程序员
王晓枫5 小时前
flutter接入三方库运行报错:Error running pod install
前端·flutter
符方昊5 小时前
React 19 对比 React 16 新特性解析
前端·react.js
ssshooter5 小时前
又被 Safari 差异坑了:textContent 拿到的值居然没换行?
前端
曲折5 小时前
Cesium-气象要素PNG色斑图叠加
前端·cesium
Forever7_5 小时前
Electron 淘汰!新的桌面端框架 更强大、更轻量化
前端·vue.js
不会敲代码15 小时前
前端组件化样式隔离实战:React CSS Modules、styled-components 与 Vue scoped 对比
css·vue.js·react.js
Angelial5 小时前
Vue3 嵌套路由 KeepAlive:动态缓存与反向配置方案
前端·vue.js
jiayu6 小时前
Angular学习笔记24:Angular 响应式表单 FormArray 与 FormGroup 相互嵌套
前端