Vue 组件中的 data 为什么是函数?

如何改变文本的样式

一、总结

1.vue中组件是用来复用的,为了防止data复用,将其定义为函数。

2.vue组件中的data数据都应该是相互隔离,互不影响的,组件每复用一次,data数据就应该被复制一次,之后,当某一处复用的地方组件内data数据被改变时,其他复用地方组件的data数据不受影响,就需要通过data函数返回一个对象作为组件的状态。

3.当我们将组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,拥有自己的作用域,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。

4.当我们组件的date单纯的写成对象形式,这些实例用的是同一个构造函数,由于JavaScript的特性所导致,所有的组件实例共用了一个data,就会造成一个变了全都会变的结果。

二、代码分析:

vue每次会通过组件创建出一个构造函数,每个实例都是通过这个构造函数new出来的

假如data是一个对象,将这个对象放到这个放到原型上去

function VueComponent(){}
VueComponent.prototype. o p t i o n s = d a t a : n a m e : ′ t h r e e ′ l e t v c 1 = n e w V u e C o m p o n e n t ( ) ; v c 1. options = { data:{name:'three'} } let vc1 = new VueComponent(); vc1. options=data:name:threeletvc1=newVueComponent();vc1.options.data.name = ‘six’; // 将vc1实例上的data修改为six
let vc2 = new VueComponent(); // 在new一个新的实例vc2
console.log(vc2.$options.data.name); six
// 输出vc2的data的值是six,这时候发现vc2中的data也被修改了,他们data相互影响
将data改为一个函数

// 这样就可以保证每个组件调用data返回一个全新的对象,和外部没有关系
function VueComponent(){}
VueComponent.prototype. o p t i o n s = d a t a : ( ) = > ( n a m e : ′ t h r e e ′ ) l e t v c 1 = n e w V u e C o m p o n e n t ( ) ; l e t o b j D a t a = v c 1. options = { data: () => ({name:'three'}) } let vc1 = new VueComponent(); let objData = vc1. options=data:()=>(name:three)letvc1=newVueComponent();letobjData=vc1.options.data()
objData.name = ‘six’; // 调用data方法会返回一个对象,用这个对象作为它的属性
console.log(objData)
let vc2 = new VueComponent();
console.log(vc2.$options.data());

三、源码

1.在core/global-api/extend.js中会调用mergeOptions方法
const Sub = function VueComponent (options) {
this._init(options)
} // 创建一个子类
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions( // 调用mergeOptions进行合并
Super.options,
extendOptions
)
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
2.mergeOptions方法(src/core/util/options.js)
// 将两个对象合并到一个对象中
// 包括生命周期的合并,data的合并等
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== ‘production’) {
checkComponents(child)
}

if (typeof child === ‘function’) {
child = child.options
}

normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
if (!child._base) {
// 对extend和mixins做合并
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}

const options = {}
let key
// 循环父类
for (key in parent) {
mergeField(key)
}
// 循环子类
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
// 合并字段
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
3.合并data(src/core/util/options.js)
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
}
// 如果是函数就调用mergeDataOrFn进行合并
return mergeDataOrFn(parentVal, childVal)

}

return mergeDataOrFn(parentVal, childVal, vm)
}

4.源码总结:
通过vue.extend方法创建一个子类,创建子类之后会把自己的选项和父类的选项使用mergeOptions方法做一个合并,自己的选项就包含data。在mergeOptions中会调用strats.data对子类的data进行合并,这个方法中首先会判断子类的data进行判断,要求data必须是一个函数,如果不是会报错告诉它这个data应该是一个函数定义,因为每一个组件都会返回一个实例,如果是data就会调用 mergeDataOrFn方法进行合并。然后会合并父类的extend、minin、use方法,最后extend返回的就是这个子类的方法。

补充:

(1) 为什么要合并?因为子组件也要有父组件的属性,extend方法是通过一个对象创建了一个构造函数,但是这个构造函数并没有父类的属性,因为它是一个新函数,和之前的Vue构造函数是没有关系的。通过extend产生了一个子函数,这个子函数需要拥有vue实例上的所以东西,它就要做一次合并。

四、为什么new Vue这个里面的data可以放一个对象?

因为这个类创建的实例不会被复用。它只会new一次,不用考虑复用。

Logo

前往低代码交流专区

更多推荐