Background of 0.

Currently, IN the business of Dingxiang Doctor, I will be in charge of a WebApp project based on Vue family bucket.

It’s always been a bit confusing: in the template tag of each component, a dataReady is used for rendering control. For example, render the page after the request is complete, as shown below.

## Template section
<template>
  <div class="wrap"
       v-if="dataReady">
  </div>
</template>

Part # # Script

  async created() {
    await this.makeSomeRequest();
    this.dataReady = true;
  },
Copy the code

But in fact, I did not define the dataReady property in the data option of the component.

So, I looked up the entry file main.js, and there was this sentence

  Vue.mixin({
    data() {
      return {
        dataReady: false}; } // omit below});Copy the code

Why can a variable defined globally be used in every component? How does Vue do it?

So, after turning over a pile of data and source code, a little answer.

1. Pre-knowledge

As part of the pre-knowledge is very complicated to explain, I will directly give it in the form of conclusion:

  • Vue is a constructor that passesnew VueWhat you create is a root instance
  • All single-file components are subclasses of Vue. Extend.
  • Each component rendered in the parent component’s template tag, or in the Render function, is an instance of the corresponding subclass.

2. Start with vue.mixin

The source code looks like this:

  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
Copy the code

Simply extend the current context object’s options and the parameters passed in.

The mergeOptions function extends the Vue static options property.

So let’s see what mergeOptions does.

3. Use mergeOptions to mergeOptions on Vue classes

Find the mergeOptions source code and keep it in mind.

export functionmergeOptions ( parent: Object, child: Object, vm? : Component): Object {// There is a long line of code in the middle, which is not related to the data property. const options = {}let key
  for (key in parent) {
    mergeField(key)
  }
  for (key inChild) {// Check to see if the merge has already been performed. If the merge has already been performed, there is no need to merge againif(! hasOwn(parent, key)) { mergeField(key) } }function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}
Copy the code

The mergeOptions function executes the mergeField function by iterating through its properties on the passed options object, and returns a new options.

The question then becomes: What did mergeField do? Let’s look at the code.

/ / find merge strategy function const strat = strats [key] | | defaultStrat / / perform merge strategy function options [key] = strat (parent [key], child [key], vm, key)Copy the code

Now, as I recall,

  • What is parent? — In this case, vue.options
  • What is a child? Yes, that’s the argument object passed in when the mixin method is used.
  • So what’s key? — is the key of an attribute on a parent or child object.

Ok, we can confirm that the Child object must contain a property whose key is data.

Okay, let’s find out what Strats.data is.

strats.data = function// parentVal, in this case, is the data attribute on Vue's own options. ParentVal may not exist: Any, // childVal, in this case, is the data property in the option object passed in by the mixin method. ChildVal: any, vm? : Component ): ? Function {// If you recall the Vue. Mixin code, the VM is emptyif(! vm) {if(childVal && typeof childVal ! = ='function') {// Does this mistake look familiar? Think about it if you were mixin, if you passed in data that wasn't a function, did you get an error? process.env.NODE_ENV ! = ='production' && warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.',
        vm
      )

      returnParentVal} // The return value of this statement will be the value of options.data in the mergeField function.returnMergeDataOrFn (parentVal, childVal)} // In this example, the following line is not executed, why? Think for yourself.return mergeDataOrFn(parentVal, childVal, vm)
}
Copy the code

OK, so let’s see what mergeDataOrFn is.

export functionmergeDataOrFn ( parentVal: any, childVal: any, vm? : Component ): ? Function {if(! Vm) {// childVal is the data property in the argument to the mixin method just now, a functionif(! childVal) {return// parentVal is a Vue. Options. data attribute, but Vue does not have its own data attributeif(! parentVal) {returnChildVal} // }else{// You are not passing vm parameters}}Copy the code

So, maybe that’s what it’s all about

Vue.options.data = function data() {return {
        dataReady: false}}Copy the code

4. From Vue classes -> subclasses

The data attribute is added to Vue. Options. How come the single file component of Vue can be used in its instance?

This is the vue. extend function, which is used to extend the subclass, usually we write a SFC single file component, in fact, are Vue class subclasses.

  Vue.extend = function(extendOptions: Object): Function {const Super = this // you don't have to care about some code const Sub =functionVueComponent (options) {this._init(options)} // Inherit sub.prototype = object.create (super.prototype) Sub. Prototype. Constructor = Sub Sub. Cid = cid++ / / note here also carried out the options function, do the merge option. Sub.options = mergeOptions(super. options, extendOptions) // You don't have to worry about some code in the middle // subclasses are returned.return Sub;
}
Copy the code
  • What are extendOptions?

It’s basically what we’re writing in a single file component, and it might look like this

exportDefault {// Of course, there may be no data functiondata() {return{
            id: 0
        }
    },
    methods: {
        handleClick() {}}}Copy the code
  • What are super. options?

In our project, there is no multiple inheritance relationship like Vue -> Parent -> Child, so we can assume that super. options is the same as Vue.

Remember? Vue. Options has a data attribute after execution of Vue. Mixin.

5. MergeOptions for Vue classes -> subclasses

So let’s look at that

Sub.options = mergeOptions(
  Super.options,
  extendOptions
)
Copy the code

Let’s go back to the mergeOptions function.

export functionmergeOptions ( parent: Object, child: Object, vm? : Component): Object {// Omit some of the above checks and normalizes 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
}
Copy the code

As before, an options is returned and given to sub. options.

The options.data property is still executed by the strats.data policy function, but the process is not the same this time.

Note that parentVal is vue.options. data, while childVal may be a data function or may be null. Why is that? Ask extendOptions, the parameters it passed.

strats.data = function( parentVal: any, childVal: any, vm? : Component ): ? Function {if(! vm) {if(childVal && typeof childVal ! = ='function') {// omit} // No problem.return mergeDataOrFn(parentVal, childVal)
  }

  return mergeDataOrFn(parentVal, childVal, vm)
}
Copy the code

Return mergeDataOrFn(parentVal, childVal).

Let’s look at this mergeDataOrFn.

First assume that childVal is empty.

export functionmergeDataOrFn ( parentVal: any, childVal: any, vm? : Component ): ? Function {if(! Vm) {// return at this pointif(! childVal) {return parentVal
    }
  } else{// omit}}Copy the code

So if extendOptions does not pass the data attribute (a function), it will use parentVal (vue.options.data).

Therefore, it can be simply understood as

Sub.options.data = Vue.options.data = function data() {return {
        dataReady: false}}Copy the code

What if extendOptions passes a data function? We can keep looking in mergeDataOrFn

    return function mergedDataFn () {
      return mergeData(
        typeof childVal === 'function' ? childVal.call(this, this) : childVal,
        typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      )
    }
Copy the code

The return is a function. Considering that childVal and parentVal are both functions, we can simplify the code

// Now suppose the subclass's data option looks like thisfunction subData() {return{
            id: 0
        }
}

function vueData() {return {
        dataReady: false}} // what does Sub get? Sub.options.data =function data() {return mergeData(
        subData.call(this, this),
        vueData.call(this, this)
    )
}

Copy the code

Please think about what this is and tell you at the end.

Sub.options.data is executed when the Sub class is instantiated once. So you’re going to get something of this form.

return mergeData({ id: 0 }, { dataReady: false })
Copy the code

The principle of mergeData is also very simple: traversal key + deep merge; If the key has the same name, the override is not performed. Take a look at the mergeData function, which is not the focus of this article.

Specifically how to perform instantiation, how to execute the data function, interested in their own to understand, briefly, and three functions related:

  • Vue.prototype._init
  • initState
  • initData

The end of the 7.

Now do you understand why every component has a dataReady: false?

In fact, a word summed up, is: The data function on the Vue class (I’ll call parentDataFn) is merged with the data function of the subclass (I’ll call childDataFn) to create a new function. This new function is executed when the subclass is instantiated, and both parentDataFn and childDataFn are executed. And returns the merged data object.

By the way, just now

Sub.options.data = function mergedDataFn() {return mergeData(
        subData.call(this, this),
        vueData.call(this, this)
    )
}
Copy the code

Here this is an instance of the Sub class.

8. Conclusion

To be honest, I used to write some articles after finishing my work so that I could better understand what I had learned. For example:

  • Linux front and background processes switch
  • What happens in the new process?

But these are simple “skill notes” or “basic inquiry”.

This is my first attempt to understand a complex system such as Vue source code, and I am worried that many places will be misleading, so thanks for the following references:

  • Parsing MergeOption: Inside Vue Technology
  • Application of vue-mixin merging: Use mixins in vue.js.

If anything is not quite right, please add more comments.

Finally, the Lilac Doctor front end team is hiring.

Team introduction is here.

If you have any questions about the recruitment, you can write to the author on Zhihu.

Author: Kevin Wong, Front End Engineer of DAC Garden