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.