为什么 data 必须是个函数
Vue 组件选项式 API 中的 data 定义为对象时会报错,为什么 data 必须是个函数呢?
旧版 Vue 文档的解释如下:
一、对象 data 与函数 data 的区别
上面问题跟 JS 的原型链知识有关。
1、对象 data
先来看对象 data 的情况:
function Component() {
}
Component.prototype.data = {
name: 'Morty',
age: 14,
}
const componentA = new Component()
const componentB = new Component()
componentA.data.age = 40
console.log(componentA, componentB)
输出结果:
可以看到,componentA 与 componentB 的 data 都指向同一个内存地址,当 A 的 age 变为 40 时,B 也跟着变了。
同理,对象 data 会使得所有组件实例共用一份 data,造成一变全变的结果。
2、函数 data
接下来用函数改造上面的代码:
function Component() {
this.data = this.data()
}
Component.prototype.data = {
Component.prototype.data = function () {
return {
name: 'Morty',
age: 14,
}
}
const componentA = new Component()
const componentB = new Component()
componentA.data.age = 40
console.log(componentA, componentB)
输出结果:
可以看到,componentA 与 componentB 的 data 是独立的。
同理,函数 data 的每个组件实例都是独立的,互不影响。
注意
new Vue 的实例是不会被复用的,因此不存在引用对象的问题。
二、data 源码分析
下面从 data 的源码进行分析:
src/core/instance/state.ts
// https://github.com/vuejs/vue/blob/main/src/core/instance/state.ts#L122
function initData(vm: Component) {
let data: any = vm.$options.data
data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
// ...
}
这里 data 既可以是函数也可以是对象,在 new Vue 初始化时 会对选项进行合并:
src/core/instance/init.ts
// https://github.com/vuejs/vue/blob/main/src/core/instance/init.ts#L45
// 合并 Vue 属性
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor as any),
options || {},
vm
)
在定义 data 阶段,会进行校验:
src/core/util/options.ts
// https://github.com/vuejs/vue/blob/main/src/core/util/options.ts#L127
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): Function | null {
if (!vm) {
// 若 data 不是函数,则出现警告提示
if (childVal && typeof childVal !== 'function') {
__DEV__ &&
warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
return mergeDataOrFn(parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
三、总结
Vue 根实例对象的 data 既可以是函数,也可以是对象,因为它不会被复用。
而组件实例的 data 必须是个函数,因为函数 data 在组件实例化阶段会被调用,返回一个对象,并分配一个内存地址,实例化几次就分配几个内存地址,这些对象的地址都是独立的,所以每个组件的数据互不干扰,改变其中一个组件的状态不会影响其它组件。
总的来说,就是为了防止多个组件实例对象共用一个 data,产生数据污染。