一般在公司环境使用中,各种前端框架都会用一下,因为不同框架理念和使用场景有些许区别,有的重规模化,有的追求轻便易上手;有的模块化程度很高,有的通常全部写一起;有的规则安排的明明白白,有的又需要各种语法糖……虽然最近几个月工作特别忙,但是还是拿出了一点点时间来扩充一下
Vue
的背景知识。为什么组件的data属性必须是函数?
在自定义模块的新手上路部分,
Vue
文档是这么写的通过Vue
构造器传入的各种选项大多数都可以在组件里用。data
是一个例外,它必须是函数。如果定义了一个对象,那么Vue
会停止,并在控制台发出警告,告诉你在组件中data
必须是一个函数。
有一点觉得很奇怪,明明
new Vue()
的时候,data
是可以传入一个对象的,为什么在组件这里,data
就必须为函数了呢?简而言之,组件的配置(
options
)和实例(instance
)是需要分开的。最根本原因是**js
对于对象(以及数组等)是传引用的**,因为如果直接写一个对象进去,那么当依此配置初始化了多个实例之后,这个对象必定是多个实例共享的。举两个例子就明白了
例子1
config = {
data: {
name: 'foo'
}
};
function someComponent (config) {
this.data = config.data;
}
let c1 = new someComponent(config);
let c2 = new someComponent(config);
c1.data.name = 'bar';
console.log(c2.data.name); // 'bar'
例子2
config = {
data: function () {
return {
name: 'foo'
};
}
};
function someComponent (config) {
this.data = config.data();
}
let c1 = new someComponent(config);
let c2 = new someComponent(config);
c1.data.name = 'bar';
console.log(c2.data.name); // 'foo'
为了加深印象,还是把相关部分都扯一点。
组件(Component
)定义方式
写完
hello world
的同学都知道,组件在定义的时候,可以全局(Vue.component()
)或者局部注册new Vue({
// ...
components: {
// 将只在父模板可用
'my-component': Child
}
})
两种方法并没有本质区别,都需要在
data
属性里传入对象。局部注册只是放在了new Vue
的options
处理部分,仍然是Vue.extend(definition)
里判断。下面以全局注册为例过一遍Vue源码。
前面说的报错位置在这里
strats.data // vue-template-compiler/build.js
...
if (typeof childVal !== 'function') {
"development" !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
...
这个函数简单来说,是负责
data
字段内容处理的,不管是new Vue
的参数里data
还是组件初始化的data
,都要经过这里。简单起见,从这个位置往上倒(二声)到开头:
initGlobalAPI (Vue)
-function initAssetRegisters (Vue)
...
到这一步结束,按照
_assetTypes
(包括'component','directive','filter'
)挂载方法。这里挂载了Vue.component
的初始化方法,但还没调用。经过一众其他内部操作。直到执行我们的代码(组件是从官方文档抄过来的)
Vue.component('button-counter', {
data: function () {
return {
counter: 0
}
}
template: 'You clicked me {{ count }} times.'
})
-Vue.extend(definition)
definition内容:
{
data: function () {...},
"template": "You clicked me {{ count }} times.",
"name": "button-counter"
}
写入组件的配置:
Sub.options = mergeOptions(Super.options, definition)
-mergeField('data')
-strats.data
这一步就是前面报错的那一步,会判断
data
是否为函数,是则执行并挂载函数方法。否则返回父级属性。
vue-component-config
组件挂载结果
看起来3个
_assetTypes
加了s,是在代码里搞的config._assetTypes.forEach(function (type) {
strats[type + 's'] = mergeAssets
})
在实际使用时,才会初始化组件,即调用
function VueComponent (options) {
this._init(options)
}
初始化Vue实例时的处理
new Vue
的时候,也会调用mergeOptions
,不同的是这时候传入了vm
实例。这时在mergeField('data')
里走了另外一条路线:return function mergedInstanceDataFn () {
// ...
var instanceData = typeof childVal === 'function'
? childVal.call(vm)
: childVal
// ...
}