合并配置
通過之前的原始碼學習,我們已經了解到了new Vue主要有兩種場景,第一種就是在外部主動呼叫new Vue創建一個實體,第二個就是代碼內部創建子組件的時候自行創建一個new Vue實體,但是無論那種new Vue方式,我們都需要進入了Vue._init,執行mergeOptions函式合并配置,為了更直觀,我們整個demo除錯耍耍,
// src\main.js
let childComp = {
template:"<div>{{msg}}</div>",
data(){
return{
msg:"childComp"
}
},
created(){
console.log("childComp created");
},
mounted(){
console.log("childComp mounted");
}
}
Vue.mixin({
created(){
console.log("mixin");
}
})
let app = new Vue({
el:"#app",
render: h => h(childComp)
})
我用的時vue-cli3,這里有個小細節需要注意一下,vue-cli3開發環境默認使用的是runtime版本(node_modules\vue\dist\vue.runtime.esm.js),這個版本是不支持編譯template的,需要用Compiler版本,這個在vue.config.js中配置一下即可,配置代碼如下:
module.exports = {
runtimeCompiler: true
}
準備作業搞好了,那么我們現在開始進入_init函式,看看合并配置是怎么一個說法,
// src\core\instance\init.js
Vue.prototype._init = function (options?: Object) {
...
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), //vue.options
options || {}, //new Vue中的options
vm
)
}
...
}
外部呼叫場景
上述代碼中可明顯看出兩中合并配置的情況,我們一開始進入的肯定時非組件模式,也就是else情況,mergeOptions傳入了3個入參,我們先看第一個入參的resolveConstructorOptions方法做了什么,
// src\core\instance\init.js
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
入參Ctor = vm.constructor = Vue,Vue沒有父級,所以不會進入到if邏輯,因此這里回傳的就是Vue.options的配置,Vue.options則在初始化的時候就做了定義和配置,
// src\core\global-api\index.js
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
Vue.options._base = Vue //createComponent時用到,之前提及過,
extend(Vue.options.components, builtInComponents) //擴展一些內置組件
這里ASSET_TYPES在src\shared\constants.js有定義
// src\shared\constants.js
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
然后我們再回傳去_init函式分析一下mergeOptions函式:
// src\core\util\options.js
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
...
const options = {}
let key
、、
for (key in parent) {
mergeField(key)
}
for (key in child) {
// key沒在parent定義時
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
}
簡略了部分代碼,我們先去關注合并的關鍵代碼,
這邊其實就是遍歷了parent(Vue.options)和child(new Vue中的options),然后遍歷的程序中呼叫了mergeField方法,而該方法先去拿到一個strat函式,這個函式首先是再strats中去找,沒找到就使用defaultStrat默認函式(defaultStrat可自行查閱原始碼),我們主要看strats:
// src\core\util\options.js
const strats = config.optionMergeStrategies
strats是定義在config中,所以說我們是可以隨意改動strats的,然后在options.js中,strats擴展了很多屬性,每個屬性(key)都是一種合并策略,有興趣的可以一個個研究,因為我們例子是生命周期的合并,所以我們先挑生命周期的合并策略來分析,后面遇到其他的再做分析,
// src\core\util\options.js
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
LIFECYCLE_HOOKS定義在src\shared\constants.js
// src\shared\constants.js
export const LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured'
]
遍歷這些值,然后定義它們的合并策略,其實都mergeHook方法,都是一樣的合并策略,下面我們看看mergeHook函式:
// src\core\util\options.js
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
這個多層嵌套的三元運算式看著復雜,其實不難,我們可以分段理解:
①:childVal有值:進入②,
childVal沒值:賦值parentVal;
②:parentVal有值:parentVal和childVal陣列合并,
parentVal沒值:進入③;
③:childVal是個陣列:賦值childVal,
childVal不是陣列:賦值[childVal];
最終我們return了一個陣列到mergeOptions函式,
現在我們回過頭來demo中的Vue.mixin定義,其原始碼其實也呼叫了mergeOptions,我們看看原始碼:
// src\core\global-api\mixin.js
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
mixin的原始碼很簡單,其實就是呼叫了mergeOptions對Vue.options做了合并,有個小細節需要留意,就是demo中Vue.mixin和new Vue的代碼順序,必須先對Vue.mixin做出定義,不然在new Vue的時候Vue.options和new Vue的options合并時,是會丟失掉Vue.mixin的,因為那時候Vue.mixin并沒有執行mergeOptions把options合并到Vue.options上,
組件場景
接下來我們看另一種情況,組件合并配置,也就是在_inti方法中運行了initInternalComponent函式,我們來分析一下它做了什么?
// src\core\instance\init.js
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 = https://www.cnblogs.com/YmmY/archive/2022/02/20/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
}
}
子組件的合并就相對簡單很多了,vm.$options去繼承了子組件構造器vm.constructor.options,然后再把一些配置掛載到上面,我們主要看看vm.constructor.options是怎么來的,
// src\core\global-api\extend.js
Vue.extend = function (extendOptions: Object): Function {
const Super = this
...
const Sub = function VueComponent (options) {
this._init(options)
}
// 構造器指向自己
Sub.prototype.constructor = Sub
// 合并配置
Sub.options = mergeOptions(
Super.options,
extendOptions
)
...
}
其實Vue.extend的時候對子組件的構造器進行了定義了,還對Vue.options(Super.options)和子組件的options(extendOptions)做了合并,
所以initInternalComponent中的vm.$options其實就是一個已經把Vue.options和子組件的options合并好的配置集合了,
總結
至此Vue的options合并就告一段落了,我們需要知道它有兩個場景,外部呼叫場景和組件場景,
其實一些庫、框架的設計也是類似的,都會有自身的默認配置,同時又允許在初始化的時候讓開發者自定義配置,之后再合并兩個配置來達到應付各種場景需求,這種設計思想也是我們寫組件或做架構的時候必不可少的思維模式,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/429277.html
標籤:其他
