背景
mixins的一般用法
就我自己個人而言,mixins一般用的比較多的就是定義一個混入對象,然后在組件里或者新建的vue對象里使用mixins,用法簡單明了厅目。基本上和官網(wǎng)用法一樣法严,這里以官網(wǎng)示例為例:
// 定義一個混入對象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定義一個使用混入對象的組件
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
VM1740:8 hello from mixin!
全局使用mixins
直到最近想寫一個通用業(yè)務(wù)(埋點)损敷,希望能在公共文件引入,不需要改動其他文件的前提下深啤,比如統(tǒng)計進(jìn)入頁面拗馒、離開頁面的時間,各個生命周期的時間等等墓塌。很不湊巧瘟忱,我用了全局mixins,才發(fā)現(xiàn)原來所有的vue實例都會被統(tǒng)計到苫幢,不僅僅是當(dāng)前頁面的vue實例访诱,而是當(dāng)前頁面所有用到的子組件都會被統(tǒng)計到。而全局mixins使用的警告韩肝,官網(wǎng)早已經(jīng)告知過触菜,果然什么都要自己試試才會印象深刻,之前也看到過這段文字警告:
也可以全局注冊混入對象哀峻。
但是注意使用涡相!
一旦使用全局混入對象,將會影響到 所有 之后創(chuàng)建的 Vue 實例剩蟀。
使用恰當(dāng)時催蝗,則可以為自定義對象注入處理邏輯。
還是古人有智慧育特,“紙上得來終覺淺丙号,絕知此事要躬行”!
mixin原理
這就引起了我的好奇缰冤,全局的mixins為什么會影響到所有的子組件犬缨?為此特地下載了最新的vue 源碼,2.6.8版本棉浸,查看了下源碼怀薛,才發(fā)現(xiàn)有部分語法沒見過,原來是flow的語法檢查迷郑,嗯枝恋,很抱歉用了這么久的2.x 版本的vue竟然不知道這個。
mixins文件代碼很少嗡害,如下:
import { mergeOptions } from '../util/index'
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
可以看出鼓择,其實就是把當(dāng)前Vue實例的options和傳入的mixin合并,再返回就漾。真正的實現(xiàn)是靠mergeOptions函數(shù)實現(xiàn)的呐能。
mergeOptions函數(shù)實現(xiàn)
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)
//上面的代碼可以略過不看,主要是檢查各種格式的
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
//處理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) {
//這里parent是this.options
mergeField(key)
}
for (key in child) {
//這里child是mixins抑堡,同時檢查parent中是否已經(jīng)有key即是否合并過
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
}
上面的注釋已經(jīng)標(biāo)明摆出,首先我們要明確這個函數(shù)傳進(jìn)去的兩個參數(shù)分別是this.options 和 mixin,而mergeOptions函數(shù)則實現(xiàn)了遞歸遍歷this.options首妖,然后執(zhí)行mergeField偎漫,返回最終合并的this.options。到這里基本上就是mixin的所有實現(xiàn)了有缆。但是mergeField函數(shù)看似簡單象踊,實際上是很重要的温亲,我還是很好奇這個函數(shù)的實現(xiàn)的。
mergeField函數(shù)實現(xiàn)
const strats = config.optionMergeStrategies
//默認(rèn)的合并策略
const defaultStrat = function (parentVal: any, childVal: any): any {
return childVal === undefined
? parentVal
: childVal
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
于是我又在代碼里找了找杯矩,發(fā)現(xiàn)strats這個對象有很多屬性栈虚,如下:
strats.el = strats.propsData = function (parent, child, vm, key) {
strats.data = function (
strats.watch = function (
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
.....
.....
這個時候就很清晰了,一般我們執(zhí)行mergeField 里的key基本上就是上面strats的屬性了史隆,用的最多的可能就是data魂务、methods、props了泌射。所以如果我們在mixins中用到了data粘姜,其本質(zhì)上就是合并當(dāng)前vue實例對象里的data和我們傳進(jìn)去的mixin里的data,其他屬性也是一樣的熔酷,只是合并策略還需深入研究孤紧。
不得不感嘆,vue官網(wǎng)真的沒有任何廢話拒秘,官網(wǎng)其實早已給出了選項合并策略坛芽。
- 當(dāng)組件和混入對象含有同名選項時,這些選項將以恰當(dāng)?shù)姆绞交旌弦砜佟1热缌瑪?shù)據(jù)對象在內(nèi)部會進(jìn)行遞歸合并,在和組件的數(shù)據(jù)發(fā)生沖突時以組件數(shù)據(jù)優(yōu)先阴颖。
- 值為對象的選項活喊,例如 methods, components 和 directives,將被混合為同一個對象量愧。兩個對象鍵名沖突時钾菊,取組件對象的鍵值對。
而回到代碼層面上偎肃,如果是key為data的話煞烫,代碼實現(xiàn)如下:
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)
}
如果key是 methods, components 和 directives
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): ?Object {
if (childVal && process.env.NODE_ENV !== 'production') {
assertObjectType(key, childVal, vm)
}
if (!parentVal) return childVal
const ret = Object.create(null)
extend(ret, parentVal)
if (childVal) extend(ret, childVal)
return ret
}
組件局部注冊的原理
其實到這里,我依然不知道為什么全局mixins會影響到所有的子組件累颂,直到我發(fā)現(xiàn)了這段代碼:
function initAssetRegisters (Vue) {
/**
* Create asset registration methods.
*/
ASSET_TYPES.forEach(function (type) {
Vue[type] = function (
id,
definition
) {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (type === 'component') {
validateComponentName(id);
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id;
definition = this.options._base.extend(definition);
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition };
}
this.options[type + 's'][id] = definition;
return definition
}
};
});
}
直到看到這段代碼滞详,終于明白了為何會影響所有子組件了,局部注冊組件紊馏,本質(zhì)上其實是Vue.extends().而extend里面最終也會執(zhí)行mergeOptions()函數(shù)料饥。至此,終于明白了全局mixins的影響以及實現(xiàn)原理朱监。不過岸啡,extend就不再在這里多聊了,等我下次把extend看明白了赫编,再總結(jié)吧巡蘸。