preface

There have been a lot of articles about Vue tips lately, and I wrote one myself (” Manipulation “in Vue development), but the tips in this article can be found in the Vue documentation, and some of the tips in this article are not found in the Vue documentation at all! Why is that?

As I began to read the source code, I realized that these so-called skills were nothing more than understanding the source code.

Let me share with you what I’ve learned.

Hidden in the source code skills

We know that when we use Vue, we call it with the new keyword, which means that Vue is a constructor. So the source is where the Vue constructor is defined!

In the SRC/core/instance/index. Js found in the constructor

function Vue (options) {
  if(process.env.NODE_ENV ! = ='production' &&
! (this instanceof Vue)  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
 }  this._init(options) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) Copy the code

In the constructor, only one thing is done — this._init(options).

The _init() function is defined in initMixin(Vue)

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function(options? : Object) {/ /... The body of the _init method is omitted here  }
}
Copy the code

With that in mind, let’s see what fun tricks you can use in the process.

Deconstruct the parameters assigned to the child component data

According to the official documentation, we usually write the child component data option like this:

props: ['parentData'].data () {
  return {
    childData: this.parentData
  }
} Copy the code

But you know what, you could also write this:

data (vm) {
  return {
    childData: vm.parentData
  }
}
// Or use destruct assignment data ({ parentData }) {  return {  childData: parentData  } } Copy the code

Pass the variables in props to the data function by destructuring the assignment. That is, the parameters of the data function are the current instance object.

This is because the data function is enforced by binding the current instance object with the call() method. This happened during the data merge phase, so go ahead and see if there’s something else!

The _init() function mainly performs a series of initializations, of which the merging of options is the basis.

vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
)
Copy the code

The $options attribute is added to the Vue instance, and all of the initialization methods use the instance’s $options attribute, namely vm.$options.

The merging of data is done in mergeOption.

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)  }   return mergeDataOrFn(parentVal, childVal, vm) } Copy the code

The above code is the merge strategy function of the data option. First, the parent component is determined by determining whether vm exists. If vm exists, the parent component is determined. In any case, the result of the mergeDataOrFn execution is returned. The difference is that the parent component is passed through the VM.

Next look at the mergeDataOrFn function.

export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
vm? : Component) :? Function { if(! vm) { // in a Vue.extend merge, both should be functions  if(! childVal) { return parentVal  }  if(! parentVal) { return childVal  }  // when parentVal & childVal are both present,  // we need to return a function that returns the  // merged result of both functions. no need to // check if parentVal is a function here because  // it has to be a function to pass previous merges.  return function mergedDataFn () {  return mergeData(  typeof childVal === 'function' ? childVal.call(this, this) : childVal,  typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal  )  }  } else {  return function mergedInstanceDataFn () {  // instance merge  const instanceData = typeof childVal === 'function'  ? childVal.call(vm, vm)  : childVal  const defaultData = typeof parentVal === 'function'  ? parentVal.call(vm, vm)  : parentVal  if (instanceData) {  return mergeData(instanceData, defaultData)  } else {  return defaultData  }  }  } } Copy the code

The function as a whole is composed of if judgment branch statement blocks to judge the VM, which also enables mergeDataOrFn to distinguish between parent and child components.

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 mergedDataFn function is returned when the parent component’s data option exists. The mergedDataFn function returns the mergeData function.

Call (this, this); parentVal.call(this, this); call(this, this); The first this specifies the scope of the data function, and the second this is the argument passed to the data function. That’s how you can start with deconstructing assignments.

Keep reading!

Note that the mergedDataFn function has not been executed because the function has already returned.

This is what happens when you process the child component’s data option. You can see that the child component’s options always return a function.

Now that we’re done with the sub-component options, let’s look at the non-sub-component options, which are created using the new operator.

if(! vm) {.} else {
  return function mergedInstanceDataFn () {
    // instance merge
 const instanceData = typeof childVal === 'function'  ? childVal.call(vm, vm)  : childVal  const defaultData = typeof parentVal === 'function'  ? parentVal.call(vm, vm)  : parentVal  if (instanceData) {  return mergeData(instanceData, defaultData)  } else {  return defaultData  }  } } Copy the code

If you do the else branch then you just return mergedInstanceDataFn. The parent component data option function is also executed using the call(VM, VM) method, forcing binding of the current instance object.

const instanceData = typeof childVal === 'function'
  ? childVal.call(vm, vm)
  : childVal
const defaultData = typeof parentVal === 'function'
  ? parentVal.call(vm, vm)
 : parentVal Copy the code

Note that the mergedInstanceDataFn function is also not executed at this point. So the mergeDataFn function always returns a function.

Why is it so important to return a function? So strats.data turns out to be a function?

This is because the data objects returned by functions ensure that each component instance has a unique copy of the data, preventing data interaction between components.

This mergeDataFn is handled by the subsequent initialization phase. MergeDataFn returns the result of the execution of mergeData(childVal, parentVal), which is the data option that actually merges parent and child components. This is because the props and Inject options are initialized before the Data option. This ensures that the data in data can be initialized using props.

Call props or inject in the data option!

Lifecycle hooks can be written as arrays

Life cycle hooks can be written as arrays, you can try!

created: [
    function () {
      console.log('first')
    },
    function () {
 console.log('second')  },  function () {  console.log('third')  } ] Copy the code

How can you write that? Let’s look at the merge handling of lifecycle hooks!

Mergehooks are used to merge lifecycle hooks.

/ * * * Hooks and props are merged as arrays.
* /
function mergeHook ( parentVal: ? Array<Function>,childVal: ? Function | ? Array<Function>): ?Array<Function> {  return childVal  ? parentVal  ? parentVal.concat(childVal)  : Array.isArray(childVal)  ? childVal  : [childVal]  : parentVal }  LIFECYCLE_HOOKS.forEach(hook= > {  strats[hook] = mergeHook }) Copy the code

Mergex Hooks and props are merged as arrays

LIFECYCLE_HOOKS using forEach to iterate through the LIFECYCLE_HOOKS constant shows that LIFECYCLE_HOOKS are an array. LIFECYCLE_HOOKS come from the shared/ Constants.js file.

export const LIFECYCLE_HOOKS = [
  'beforeCreate'.  'created'.  'beforeMount'.  'mounted'. 'beforeUpdate'. 'updated'. 'beforeDestroy'. 'destroyed'. 'activated'. 'deactivated'. 'errorCaptured' ] Copy the code

So that forEach statement, which adds functions to the Strats policy object that combine the various lifecycle hook options.

return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
 : [childVal]  : parentVal Copy the code

The function body consists of three sets of ternary operators, and after being processed by the mergeHook function, the lifecycle hook functions of the component options are merged into an array.

In the first ternary operator, we check if there is a childVal, that is, if the component’s options write a lifecycle hook function, then parentVal is returned. The default assumption is that parentVal must be an array. Strats [hooks] functions do not execute at all without parentVal. Take the example of the Created lifecycle hook function:

new Vue({
    created: function () {
        console.log('created')
    }
})
Copy the code

For the Strats. created policy function, childVal is the example created option, which is a function. ParentVal should be Vue. Options. created, but Vue. Options. created does not exist, so the strats.created function returns an array:

options.created = [
  function () {
    console.log('created')
  }  
]
Copy the code

Look at the following example:

const Parent = Vue.extend({
  created: function () {
    console.log('parentVal')
  }
})
 const Child = new Parent({  created: function () {  console.log('childVal')  } }) Copy the code

Where Child is generated using new Parent, childVal is:

created: function () {
  console.log('childVal')
}
Copy the code

ParentVal is not Vue. Options. created, but parent-options. created. It is actually handled by mergeOptions inside the vue. extend function, so it should look like this:

Parent.options.created = [
  created: function () {
    console.log('parentVal')
  }
]
Copy the code

ParentVal. Concat (childVal) merges parentVal and childVal into an array. So here’s the end result:

[
  created: function () {
    console.log('parentVal')
  },
  created: function () {
 console.log('childVal')  } ] Copy the code

Note also the third ternary operator:

: Array.isArray(childVal)
  ? childVal
  : [childVal]
Copy the code

It determines whether childVal is an array, indicating that lifecycle hooks can be written as arrays. That’s the principle from the beginning!

Event listeners for lifecycle hooks

You probably don’t know what a “lifecycle hook event listener” is? Vue components can be written like this:

<child
  @hook:created="childCreated"
  @hook:mounted="childMounted"
 />
Copy the code

In initialization, the callhook(VM, ‘created’) function is used to execute an Created lifecycle function. Let’s look at how callhook() is implemented:

export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const handlers = vm.$options[hook]
  if (handlers) {
 for (let i = 0, j = handlers.length; i < j; i++) {  try {  handlers[i].call(vm)  } catch (e) {  handleError(e, vm, `${hook} hook`)  }  }  }  if (vm._hasHookEvent) {  vm.$emit('hook:' + hook)  }  popTarget() } Copy the code

The callhook() function takes two arguments:

  • Instance object;
  • The name of the lifecycle hook to call;

First cache the lifecycle function:

const handlers = vm.$options[hook]
Copy the code

If callHook(VM, created) is executed, this is equivalent to:

const handlers = vm.$options.created
Copy the code

As you saw, the lifecycle hook options are eventually merged into an array, so the resulting handlers are an array of lifecycle hooks. This code is then executed:

if (handlers) {
  for (let i = 0, j = handlers.length; i < j; i++) {
    try {
      handlers[i].call(vm)
    } catch (e) {
 handleError(e, vm, `${hook} hook`)  }  } } Copy the code

Finally, notice the code at the end of the callHook function:

if (vm._hasHookEvent) {
  vm.$emit('hook:' + hook)
}
Copy the code

Vm. _hasHookEvent is defined in the initEvents function to determine whether there is an event listener for a lifecycle hook. An initial value of false indicates that there is no event listener for a lifecycle hook. Vm. _hasHookEvent is set to true.

The event listener for the lifecycle hook, as stated at the beginning:

<child
  @hook:created="childCreated"
  @hook:mounted="childMounted"
 />
Copy the code

Listen for a component’s corresponding lifecycle hook using hook: + lifecycle hook name.

conclusion

1. The subcomponent data option function has parameters and is the current instance object;

Lifecycle hooks can be written as arrays and executed sequentially.

You can register lifecycle functions using event listeners for lifecycle hooks

However, methods not stated in official documents are not recommended.

At the end

For more articles, please go to Github, if you like, please click star, which is also a kind of encouragement to the author.