本文为原创文章,未获授权禁止转载,侵权必究!
本篇是 Vue 源码解析系列第 5 篇,关注专栏
前言
Vue 合并配置过程主要发生在 new Vue 初始化及组件创建时,初始化主要执行 _init
方法,它定义在 src/core/instance/init.js
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
)
}
// ...
}
下面我们通过案例,来分析下 Vue 的合并配置过程
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)
})
合并过程
上文我们可以看出,合并配置过程主要执行 mergeOptions
方法,该方法定义在 src/core/util/options.js
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
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
方法主要目的是把 parent
和 child
这两个对象根据一些合并策略,合并成一个新对象并返回。上述案例,vm.$options
合并后的值大致如下:
vm.$options = {
components: { },
created: [
function created() {
console.log('parent created')
}
],
directives: { },
filters: { },
_base: function Vue(options) {
// ...
},
el: "#app",
render: function (h) {
//...
}
}
根据案例,先执行 Vue.mixin
方法,它被定义在 src/core/global-api/mixin.js
,其主要目的是把 mixin
中的 created
方法混入到全局的 created
钩子函数中。
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
// 合并配置
this.options = mergeOptions(this.options, mixin)
return this
}
}
// this.options 配置
{
components: {KeepAlive: {…}, Transition: {…}, TransitionGroup: {…}}
directives: {model: {…}, show: {…}}
filters: {}
_base: ƒ Vue(options)
}
// mixin 传入的对象
{
created() {
console.log('parent created')
}
}
// 合并配置后
{
components: {}
created: [ƒ]
directives: {}
filters: {}
_base: ƒ Vue(options)
}
接着会执行 new Vue 实例,该过程会执行初始化 _init
,即调用 mergeOptions
方法合并 app 定义的配置参数,之后会打印出 parent created
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
// resolveConstructorOptions(vm.constructor) 返回结果
{
components: {}
created: [ƒ]
directives: {}
filters: {}
_base: ƒ Vue(options)
}
// options 参数
{
el: "#app"
render: h => h(childComp)
}
// 合并配置后
{
components: {}
created: [ƒ]
directives: {}
el: "#app"
filters: {}
render: h => h(childComp)
_base: ƒ Vue(options)
}
之后会执行组件配置,由于组件的构造函数是通过 Vue.extend
继承自 Vue
的,它定义在 src/core/global-api/extend.js
中
Vue.extend = function (extendOptions: Object): Function {
// 省略
// 合并配置
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// 省略
return Sub
}
// Sub.options 合并后配置如下
// 可以发现父定义了created,子定义了created,最终按先父后子顺序合并
{
components: {}
created: (2) [ƒ, ƒ]
data: ƒ data()
directives: {}
filters: {}
mounted: [ƒ]
name: "ChildComp"
template: "<div>{{msg}}</div>"
_Ctor: {}
_base: ƒ Vue(options)
}
在 Vue 组件化
文章中我们提到子组件初始化会调用 this._init(options)
, 它被定义在 src/core/instance/init.js
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
)
}
// 省略
}
组件配置合并最终会调用 initInternalComponent
方法,它被定义在 src/core/instance/init.js
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
// vm.constructor 就是子组件构造函数 Sub
// 实际等于 vm.$options = Object.create(Sub.options)
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 // 子组件父 Vue 实例
opts._parentVnode = parentVnode // 子组件父 VNode 实例
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
执行完后,options
合并后的配置大致如下:
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>'
}
}