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