這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
一、mixin是什麼
Mixin
是物件導向程式設計語言中的類,提供了方法的實現。其他類可以訪問mixin
類的方法而不必成為其子類
Mixin
類通常作為功能模組使用,在需要該功能時“混入”,有利於程式碼複用又避免了多繼承的複雜
Vue中的mixin
先來看一下官方定義
mixin
(混入),提供了一種非常靈活的方式,來分發Vue
元件中的可複用功能。
本質其實就是一個js
物件,它可以包含我們元件中任意功能選項,如data
、components
、methods
、created
、computed
等等
我們只要將共用的功能以物件的方式傳入 mixins
選項中,當元件使用 mixins
物件時所有mixins
物件的選項都將被混入該元件本身的選項中來
在Vue
中我們可以區域性混入跟全域性混入
區域性混入
定義一個mixin
物件,有元件options
的data
、methods
屬性
var myMixin = { created: function () { this.hello() }, methods: { hello: function () { console.log('hello from mixin!') } } }
元件透過mixins
屬性呼叫mixin
物件
Vue.component('componentA',{ mixins: [myMixin] })
該元件在使用的時候,混合了mixin
裡面的方法,在自動執行created
生命鉤子,執行hello
方法
全域性混入
透過Vue.mixin()
進行全域性的混入
Vue.mixin({ created: function () { console.log("全域性混入") } })
使用全域性混入需要特別注意,因為它會影響到每一個元件例項(包括第三方元件)
PS:全域性混入常用於外掛的編寫
注意事項:
當元件存在與mixin
物件相同的選項的時候,進行遞迴合併的時候元件的選項會覆蓋mixin
的選項
但是如果相同選項為生命週期鉤子的時候,會合併成一個陣列,先執行mixin
的鉤子,再執行元件的鉤子
二、使用場景
在日常的開發中,我們經常會遇到在不同的元件中經常會需要用到一些相同或者相似的程式碼,這些程式碼的功能相對獨立
這時,可以透過Vue
的mixin
功能將相同或者相似的程式碼提出來
舉個例子
定義一個modal
彈窗元件,內部透過isShowing
來控制顯示
const Modal = { template: '#modal', data() { return { isShowing: false } }, methods: { toggleShow() { this.isShowing = !this.isShowing; } } }
定義一個tooltip
提示框,內部透過isShowing
來控制顯示
const Tooltip = { template: '#tooltip', data() { return { isShowing: false } }, methods: { toggleShow() { this.isShowing = !this.isShowing; } } }
透過觀察上面兩個元件,發現兩者的邏輯是相同,程式碼控制顯示也是相同的,這時候mixin
就派上用場了
首先抽出共同程式碼,編寫一個mixin
const toggle = { data() { return { isShowing: false } }, methods: { toggleShow() { this.isShowing = !this.isShowing; } } }
兩個元件在使用上,只需要引入mixin
const Modal = { template: '#modal', mixins: [toggle] }; const Tooltip = { template: '#tooltip', mixins: [toggle] }
透過上面小小的例子,讓我們知道了Mixin
對於封裝一些可複用的功能如此有趣、方便、實用
三、原始碼分析
首先從Vue.mixin
入手
原始碼位置:/src/core/global-api/mixin.js
export function initMixin (Vue: GlobalAPI) { Vue.mixin = function (mixin: Object) { this.options = mergeOptions(this.options, mixin) return this } }
主要是呼叫merOptions
方法
原始碼位置:/src/core/util/options.js
export function mergeOptions ( parent: Object, child: Object, vm?: Component ): Object { if (child.mixins) { // 判斷有沒有mixin 也就是mixin裡面掛mixin的情況 有的話遞迴進行合併 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) // 先遍歷parent的key 調對應的strats[XXX]方法進行合併 } for (key in child) { if (!hasOwn(parent, key)) { // 如果parent已經處理過某個key 就不處理了 mergeField(key) // 處理child中的key 也就parent中沒有處理過的key } } function mergeField (key) { const strat = strats[key] || defaultStrat options[key] = strat(parent[key], child[key], vm, key) // 根據不同型別的options呼叫strats中不同的方法進行合併 } return options }
從上面的原始碼,我們得到以下幾點:
- 優先遞迴處理
mixins
- 先遍歷合併
parent
中的key
,呼叫mergeField
方法進行合併,然後儲存在變數options
- 再遍歷
child
,合併補上parent
中沒有的key
,呼叫mergeField
方法進行合併,儲存在變數options
- 透過
mergeField
函式進行了合併
下面是關於Vue
的幾種型別的合併策略
- 替換型
- 合併型
- 佇列型
- 疊加型
替換型
替換型合併有props
、methods
、inject
、computed
strats.props = strats.methods = strats.inject = strats.computed = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): ?Object { if (!parentVal) return childVal // 如果parentVal沒有值,直接返回childVal const ret = Object.create(null) // 建立一個第三方物件 ret extend(ret, parentVal) // extend方法實際是把parentVal的屬性複製到ret中 if (childVal) extend(ret, childVal) // 把childVal的屬性複製到ret中 return ret } strats.provide = mergeDataOrFn
同名的props
、methods
、inject
、computed
會被後來者代替
合併型
和並型合併有:data
strats.data = function(parentVal, childVal, vm) { return mergeDataOrFn( parentVal, childVal, vm ) }; function mergeDataOrFn(parentVal, childVal, vm) { return function mergedInstanceDataFn() { var childData = childVal.call(vm, vm) // 執行data掛的函式得到物件 var parentData = parentVal.call(vm, vm) if (childData) { return mergeData(childData, parentData) // 將2個物件進行合併 } else { return parentData // 如果沒有childData 直接返回parentData } } } function mergeData(to, from) { if (!from) return to var key, toVal, fromVal; var keys = Object.keys(from); for (var i = 0; i < keys.length; i++) { key = keys[i]; toVal = to[key]; fromVal = from[key]; // 如果不存在這個屬性,就重新設定 if (!to.hasOwnProperty(key)) { set(to, key, fromVal); } // 存在相同屬性,合併物件 else if (typeof toVal =="object" && typeof fromVal =="object") { mergeData(toVal, fromVal); } } return to }
mergeData
函式遍歷了要合併的 data 的所有屬性,然後根據不同情況進行合併:
- 當目標 data 物件不包含當前屬性時,呼叫
set
方法進行合併(set方法其實就是一些合併重新賦值的方法) - 當目標 data 物件包含當前屬性並且當前值為純物件時,遞迴合併當前物件值,這樣做是為了防止物件存在新增屬性
佇列性
佇列性合併有:全部生命週期和watch
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 }) // watch strats.watch = function ( parentVal, childVal, vm, key ) { // work around Firefox's Object.prototype.watch... if (parentVal === nativeWatch) { parentVal = undefined; } if (childVal === nativeWatch) { childVal = undefined; } /* istanbul ignore if */ if (!childVal) { return Object.create(parentVal || null) } { assertObjectType(key, childVal, vm); } if (!parentVal) { return childVal } var ret = {}; extend(ret, parentVal); for (var key$1 in childVal) { var parent = ret[key$1]; var child = childVal[key$1]; if (parent && !Array.isArray(parent)) { parent = [parent]; } ret[key$1] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child]; } return ret };
生命週期鉤子和watch
被合併為一個陣列,然後正序遍歷一次執行
疊加型
疊加型合併有:component
、directives
、filters
strats.components= strats.directives= strats.filters = function mergeAssets( parentVal, childVal, vm, key ) { var res = Object.create(parentVal || null); if (childVal) { for (var key in childVal) { res[key] = childVal[key]; } } return res }
疊加型主要是透過原型鏈進行層層的疊加
小結:
- 替換型策略有
props
、methods
、inject
、computed
,就是將新的同名引數替代舊的引數 - 合併型策略是
data
, 透過set
方法進行合併和重新賦值 - 佇列型策略有生命週期函式和
watch
,原理是將函式存入一個陣列,然後正序遍歷依次執行 - 疊加型有
component
、directives
、filters
,透過原型鏈進行層層的疊加
參考文獻
- https://zhuanlan.zhihu.com/p/31018570
- https://juejin.cn/post/6844904015495446536#heading-1
- https://juejin.cn/post/6844903846775357453
- https://vue3js.cn/docs/zh