Vue source code learning-componentization (3) merge configuration
学习内容和文章内容来自 黄轶老师
黄轶老师的慕课网视频教程地址:
"Vue.js2.0 Source Code Revealed" ,
黄轶老师拉钩教育教程地址:
"Vue.js 3.0 Core Source Code Analysis"
这里分析的源码是Runtime + Compiler 的 Vue.js
调试代码在:node_modules\vue\dist\vue.esm.js 里添加
vue版本:Vue.js 2.5.17-beta
你越是认真生活,你的生活就会越美好
——Frank Lloyd Wright
"The Fruit of Life" Classic Quotations
Click to go back to the complete list of Vue source code learning
Merge configuration
Through source code analysis section before we know, new Vue
the process typically there are two scenarios, one is outside our code automatically call new Vue(options)
the subject instantiate a Vue; the other components of our process on an internal analysis by new Vue(options)
example化子components.
In either scenario, the instance will execute _init(options)
the method, it will first perform a merge options
logic-related code src/core/instance/init.js
in:
Vue.prototype._init = function (options?: Object) {
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {
},
vm
)
}
// ...
}
We can see different scenarios for options
consolidation logic is not the same, and the incoming options
values also have a very big difference, then I will introduce separate options merger process two kinds of scenes.
To be more intuitive, we can give a simple example:
// src/main.js
import Vue from 'vue'
let childComp = {
template: '<div>{
{msg}}</div>',
created() {
console.log('child created')
},
mounted() {
console.log('child mounted')
},
data() {
return {
msg: 'Hello Vue'
}
}
}
Vue.mixin({
created() {
console.log('parent created')
}
})
let app = new Vue({
el: '#app',
render: h => h(childComp)
})
External call scene
When the execution new Vue
time, the execution this._init(options)
time will merge logic to perform the following options
:
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {
},
vm
)
Here by calling mergeOptions
to merge method, which is actually the resolveConstructorOptions(vm.constructor)
return value and options
do merge, resolveConstructorOptions
the realization is not considered, in our scenario, it is simple returns vm.constructor.options
, the equivalent Vue.options
, then the value of what is it, in fact, in initGlobalAPI(Vue)
the when defining this value, the code src/core/global-api/index.js
in:
export function initGlobalAPI (Vue: GlobalAPI) {
// ...
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
extend(Vue.options.components, builtInComponents)
// ...
}
First, by Vue.options = Object.create(null)
creating an empty object, and then traverse ASSET_TYPES
, ASSET_TYPES
the definition src/shared/constants.js
of:
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
Therefore traversal above ASSET_TYPES
code corresponds to:
Vue.options.components = {
}
Vue.options.directives = {
}
Vue.options.filters = {
}
Then it was executed Vue.options._base = Vue
, and its function was introduced when we instantiated the sub-components in the previous section.
Finally, by extend(Vue.options.components, builtInComponents)
extending the number of built-in components to Vue.options.components
on, Vue There are built-in components <keep-alive>
, <transition>
and <transition-group>
components, which is why we use other components <keep-alive>
reason components do not require registration, this child follow us <keep-alive>
assembly time will tell in detail.
So back to mergeOptions
this function, which is defined in src/core/util/options.js
the:
/**
* Merge two option objects into a new one.
* Core utility used in both instantiation and inheritance.
*/
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)
const extendsFrom = child.extends
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, 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
}
mergeOptions
The main function is to parent
and child
these two objects, according to some consolidation strategy, combined into a new object and return. More central steps,
before the recursive extends
and mixins
merged into parent
the,
then traverse the parent
call mergeField
,
and then iterate child
,
if key
not parent
on their own property, then call mergeField
.
Here is an interesting mergeField
function, which is different key
with different consolidation strategy. For example, for the life cycle function, its merge strategy is like this:
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
})
Which is LIFECYCLE_HOOKS
defined in src/shared/constants.js
the:
export const LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured'
]
This defines Vue.js all hook function name, so the hook function, their strategies are merging mergeHook
function.
Implementation of this function is also very interesting, with a multi-layer three yuan operator, the logic is
, if not present childVal
, returns parentVal
;
otherwise, then determines whether there is parentVal
, if there is put childVal
added to parentVal
return the new array; otherwise childVal
array.
So back mergeOptions
functions, once parent
and child
are defined the same hook function, then they will merge two hook function into an array.
Definition of the consolidation strategy are other attributes can src/core/util/options.js
see the file, here is not introduced one by one, interested students can see themselves.
By performing mergeField
the function, the merge results are stored into options
the object, it eventually returns.
Therefore, in our current case, after performing the following merge:
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {
},
vm
)
vm.$options
The value of is almost as follows:
vm.$options = {
components: {
},
created: [
function created() {
console.log('parent created')
}
],
directives: {
},
filters: {
},
_base: function Vue(options) {
// ...
},
el: "#app",
render: function (h) {
//...
}
}
Component scene
Since the constructor component by Vue.extend
inherited from Vue
the first look at this process, codes are defined in src/core/global-api/extend.js
the.
/**
* Class inheritance
*/
Vue.extend = function (extendOptions: Object): Function {
// ...
Sub.options = mergeOptions(
Super.options,
extendOptions
)
// ...
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({
}, Sub.options)
// ...
return Sub
}
We only retain key logic, here extendOptions
is the corresponding previously defined component objects, and it will Vue.options
merge into the Sub.opitons
middle.
Next we recall the initialization process once assembly is defined in the code src/core/vdom/create-component.js
in:
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
// ...
return new vnode.componentOptions.Ctor(options)
}
Here vnode.componentOptions.Ctor
is the point Vue.extend
of the return value Sub
, so execution new vnode.componentOptions.Ctor(options)
is then performed this._init(options)
, as options._isComponent
is true, then the merger options
process went initInternalComponent(vm, options)
logic. First look at its code to achieve, in the src/core/instance/init.js
middle:
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
initInternalComponent
The method is performed first const opts = vm.$options = Object.create(vm.constructor.options)
, here vm.constructor
is the constructor subcomponent Sub
corresponds vm.$options = Object.create(Sub.options)
.
Then again passed instantiating subunit component subassembly parent instance VNode parentVnode
parent Vue example, the subassembly parent
is saved to vm.$options
the, also retains the parentVnode
configuration as propsData
other attributes.
In this way, it initInternalComponent
just 做了简单一层对象赋值
doesn't involve complicated logic such as recursion and merge strategy.
Therefore, in our current case, after performing the following merge:
initInternalComponent(vm, options)
vm.$options
The value of is almost as follows:
vm.$options = {
parent: Vue /*父Vue实例*/,
propsData: undefined,
_componentTag: undefined,
_parentVnode: VNode /*父VNode实例*/,
_renderChildren:undefined,
__proto__: {
components: {
},
directives: {
},
filters: {
},
_base: function Vue(options) {
//...
},
_Ctor: {
},
created: [
function created() {
console.log('parent created')
}, function created() {
console.log('child created')
}
],
mounted: [
function mounted() {
console.log('child mounted')
}
],
data() {
return {
msg: 'Hello Vue'
}
},
template: '<div>{
{msg}}</div>'
}
}
to sum up
So far, Vue initialization phase for options
the consolidation process is introduced over, we need to know for options
the merger in two ways, sub-component initialization process by initInternalComponent
external initialization Vue than by way of mergeOptions
process is faster, merging process follow certain consolidation strategy, End result of the merger remains in the vm.$options
middle.
Looking at some libraries, design framework almost all similar, 自身定义了一些默认配置,同时又可以在初始化阶段传入一些定义配置
and then go to merge
the default configuration to achieve 定制化
the purpose of different needs. It's just that in the case of Vue, we will do some fine control of the merge process. Although we are not as complicated as Vue when we develop our own JSSDK, this design idea is worth learning from.
Vue source code learning-componentization (1) createComponent
Vue source code learning-componentization (2) patch
Vue source code learning-componentization (4) life cycle
Click to go back to the complete list of Vue source code learning
谢谢你阅读到了最后~
期待你关注、收藏、评论、点赞~
让我们一起 变得更强