這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、mixin是什麼 Mixin是面向對象程式設計語言中的類,提供了方法的實現。其他類可以訪問mixin類的方法而不必成為其子類 Mixin類通常作為功能模塊使用,在需要該功能時“混入”,有利於代碼復用又避免了多繼承的複雜 Vue中的mi ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
一、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