2. 根实例的data是对象,为什么组件的data是函数,组件不配拥有 对象么?
创建demo
<!DOCTYPE html>
<html>
<head>
<title>Vue_data</title>
<body>
<div id="demo">
<h3>组件data为啥是函数?</h3>
<subcomponents></subcomponents>
<subcomponents></subcomponents>
{{counter}}
</div>
<script src="../../dist/vue.js"></script>
<script>
// 创建实例
Vue.component('subcomponents', {
template: '<div @click="childCounter++">{{childCounter}}</div>',
/* data: { // 抛异常 The "data" option should be a function that returns a per-instance value in component definitions.
childCounter: 0
} */
data () {
return {
childCounter: 0
}
}
})
const app = new Vue({
el: '#demo',
data: {
counter: 1
}
});
console.log(app.$options.render);
</script>
</body>
</html>
源码位置
url src\core\instance\init.js 找到 initState => initData
init.js
// expose real self
vm._self = vm
initLifecycle(vm) // 生命周期初始化 实例属性初始化 $parent $root $refs $children
initEvents(vm) // 自定义事件处理
initRender(vm) // 插槽解析 $slots $createElement()
// 此时并未有数据
callHook(vm, 'beforeCreate')
// 接下来都是和组件状态相关的数据操作
initInjections(vm) // 注入祖辈传递的数据
initState(vm) // 找到这个宝宝 ------------------------------------- I AM HERE!!
initProvide(vm) // 传递给后代,用来隔代传递参数
callHook(vm, 'created')
-----------------------------------------------------------------------------------------
state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
// 优先级 props > methods > data > computed > watch
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
// 设置data 走data
initData(vm) // ---------------------------------------------- I AM HERE!!
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
-------------------------------------------------------------------------------------------
state.js
function initData (vm: Component) {
let data = vm.$options.data
// 如果data是函数,则执行函数 并将其返回的结果作为data选项的值
data = vm._data = typeof data === 'function'
? getData(data, vm) // 我就是棒打鸳鸯的代码
: data || {}
// proxy data on instance
// 校验,避免命名冲突
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
while (i--) {
---
}
// observe data
// 递归响应式处理
observe(data, true /* asRootData */)
}
结论: 如果组件定义对象data, data将被指向相同的对象,会造成数据污染。因此多实例时,为避免数据污染,通过工厂函数返回一个新对象。
但是就会衍生问题,为啥根实例的data可以是对象?
先去思考下,组件和跟实例声明的方式是不是不相同,根实例是通过new Vue创建的,是否是因为Vue只被创建一次,根据这个判断,我去源码中找答案
源码位置
url src\core\instance\init.js
// 合并选项: new Vue时,用户配置和系统配置合并
if (options && options._isComponent) { // 组件走这里
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else { // 根实例走这里
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
---------------------------------------------------
处理data
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
if (childVal && typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
return mergeDataOrFn(parentVal, childVal) // 组件实例没有vm
}
// 根实例存在vm
return mergeDataOrFn(parentVal, childVal, vm)
}
结论: Component => 构造函数(用来把Vue的一些方法合并到当前实例上)此时当前的组件实例并没有被创建,在合并组件时,检验data 根实例 => 构造函数 此时有vm
总结
- Vue组件可能存在多实例,如果使用对象定义data,会导致共用一个data对象,那么当状态发生改变会影响所有组件实例,由此initData时,用使用工厂函数返回全新的对象,避免数据污染
- 当Vue根实例创建时,会存在实例,而组件实例被创建时,并没有实例,并且根实例只有一个,所以不存在数据污染