在 Vue 面试或日常开发中,经常会被问到这样一个问题:为什么组件中的 data 必须是一个函数,而根实例的 data 可以是对象或函数?
本文将从 实例与组件的区别、数据污染问题、源码实现原理,以及 Vue2/3 的差异 四个角度进行深入分析。
一、实例与组件定义 data 的区别
在 Vue 根实例 中,data 属性既可以是对象,也可以是函数:
1// 对象格式 2const app = new Vue({ 3 el: "#app", 4 data: { 5 foo: "foo" 6 } 7}) 8 9// 函数格式 10const app = new Vue({ 11 el: "#app", 12 data() { 13 return { 14 foo: "foo" 15 } 16 } 17}) 18
两种写法都能正常工作。
但是在 组件中,data 只能是函数:
1// 正确写法 2Vue.component('my-component', { 3 template: [`<div>{{ foo }}</div>`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.div.md), 4 data() { 5 return { 6 foo: "foo" 7 } 8 } 9}) 10 11// 错误写法(会有警告) 12Vue.component('my-component', { 13 template: [`<div>{{ foo }}</div>`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.div.md), 14 data: { 15 foo: "foo" 16 } 17}) 18
如果直接写对象,Vue 会报出警告:
"The data option should be a function that returns a per-instance value in component definitions."
二、组件 data 必须是函数的原因
1. 数据污染问题
组件往往会被复用,可能存在多个实例。如果 data 是对象,那么这些实例会共享同一个对象,导致数据互相影响:
1function Component() {} 2Component.prototype.data = { count: 0 } 3 4const componentA = new Component() 5const componentB = new Component() 6 7componentA.data.count = 1 8console.log(componentB.data.count) // 1,被污染了! 9
也就是说,两个实例的 data 指向了同一个内存地址。
2. 使用函数返回新对象
如果 data 是函数,则每次创建实例时,都会执行一次函数,返回一个新的对象,避免了数据共享问题:
1function Component() { 2 this.data = this.data() 3} 4Component.prototype.data = function () { 5 return { count: 0 } 6} 7 8const componentA = new Component() 9const componentB = new Component() 10 11componentA.data.count = 1 12console.log(componentB.data.count) // 0,互不影响 13
因此,Vue 要求组件 data 必须是函数,以保证每个组件实例都有独立的数据空间。
三、源码分析
在 Vue 初始化数据时,data 既可以是对象,也可以是函数:
源码位置: src/core/instance/state.js
1function initData (vm) { 2 let data = vm.$options.data 3 data = vm._data = typeof data === 'function' 4 ? getData(data, vm) 5 : data || {} 6} 7
可以看到,这里允许对象或函数形式。
但是,为什么组件中却要求是函数呢?
原因在于 选项合并与校验。
源码位置: src/core/util/options.js
1strats.data = function (parentVal, childVal, vm) { 2 if (!vm) { 3 if (childVal && typeof childVal !== "function") { 4 warn( 5 'The "data" option should be a function that returns a per-instance value in component definitions.' 6 ) 7 return parentVal 8 } 9 return mergeDataOrFn(parentVal, childVal) 10 } 11 return mergeDataOrFn(parentVal, childVal, vm) 12} 13
这就是为什么 组件必须写成函数形式 的根本原因。
四、Vue2 与 Vue3 的对比
Vue2
Vue3
- 在 Vue3 的 Options API 中,规则与 Vue2 保持一致:
- 不同之处:Vue3 推出了 Composition API,使用
setup()来替代传统的data。
1export default { 2 setup() { 3 const count = ref(0) 4 return { count } 5 } 6}
- 在
setup中返回的响应式数据天然是独立的,避免了 Vue2 中必须使用函数返回对象的限制,更加直观和灵活。
五、结论与总结
- 根实例
data- Vue2 / Vue3:都可以是对象或函数。
- 根实例本身只有一个,不存在复用问题。
- 组件实例
data- Vue2 / Vue3 Options API:必须是函数,确保实例数据独立。
- Vue3 Composition API:通过
setup()定义数据,默认每个实例都独立。
- 源码机制
- Vue2 在选项合并时进行强制校验。
- Vue3 在保持兼容的同时,推荐使用 Composition API 来避免这类问题。
✅ 一句话总结
Vue2/3 的组件 data 必须是函数,以保证每个组件实例拥有独立的数据副本;在 Vue3 中使用 Composition API,更是从根本上避免了这一限制。
