by yugasun from yugasun.com/post/you-ma… 本文可全文轉載,但須要保留原做者和出處。html
咱們在實際開發過程當中,當項目愈來愈大,組件愈來愈豐富時,常常會面臨一個問題:不少組件會公用一些通用的 props
、data
和 methods
等聲明,可是也會摻雜組件本身的一些私有特有聲明,那麼咱們能不能像類的繼承同樣,來提煉和繼承呢? 固然這是能夠的,這裏能夠經過兩個基本 API extends 和 mixins 來實現。這兩個API是能夠相互替換的,惟一的區別是,extends
屬性接受的一般是個單一組件對象,而 mixins
屬性接受的是個組件對象數組。當他們只繼承單一組件時,是能夠互換的。因爲本人開發中,習慣使用 mixins
,因此本文全部實例均使用 mixins
來實現。vue
先來看看官方介紹:git
mixins
選項接受一個混入對象的數組。這些混入實例對象能夠像正常的實例對象同樣包含選項,他們將在 Vue.extend() 裏最終選擇使用相同的選項合併邏輯合併。舉例:若是你的混入包含一個鉤子而建立組件自己也有一個,兩個函數將被調用。github
簡單的理解就是Vue實例中的全部屬性配置能夠經過 mixins
實現繼承。api
簡單示例以下:數組
var mixin = {
created: function () { console.log(1) }
}
var vm = new Vue({
created: function () { console.log(2) },
mixins: [mixin]
})
// => 1
// => 2
複製代碼
假設有這麼個需求: 在某個組件渲染後向服務器端發送一個請求,進行打點
,好的,很快咱們想到 mounted
鉤子函數,而後快速的實現了需求,代碼以下:服務器
export default {
name: 'comp1',
// ...
mounted() {
console.log('Component comp1 mounted');
}
// ...
}
複製代碼
而後某一天需求變成了 某幾個 組件須要進行打點
,好的,咱們又進行了一頓猛如虎的操做,將上面代碼複製到每一個打點的組件,很快把需求搞定了。但是噩夢纔剛剛開始,過了幾天需求又變了,除了在組件渲染後須要打點,同時還須要在 created
後打點..... 此種場景是否是像極了愛情
,面對現實咱們老是在不停屈服,最終仍是忍痛把需求作了。函數
回頭冷靜思考下,其實這個打點是很廣泛的需求。若是從頭來過,咱們必定會選擇用繼承的方式來實現,而不是盲目的去愛,哦不,盲目的複製粘貼。由於咱們有 mixins
,只須要編寫一次,處處可用。那就讓咱們從頭再來一次,首先建立一個 src/minins/log.js
文件:post
export default {
created() {
console.log(`Component ${this.$options.name} created.`);
},
mounted() {
console.log(`Component ${this.$options.name} mounted.`);
},
};
複製代碼
而後在你須要的任何一個組件中引入使用:性能
import logMixin from '@/mixins/log';
export default {
name: 'comp1',
mixins: [logMixin],
// ...
}
複製代碼
一番修改後,你會發現產品經理妹子也能夠那麼迷人,是否是你又開始相信愛情了......
運行項目,打開控制檯輸出以下:
Component comp1 created.
Component comp2 created.
複製代碼
上面的需求是組件打點,如今咱們新增了需求,須要給某幾個組件添加一個通用方法 sayHello
到 methods
中,並在組件渲染後調用,可是隻是上面打點的部分組件須要添加此功能,雖然只是部分組件,但也有個上百個吧(誇張手法,切勿模仿
)。聽到這裏,你默默推開了身邊的產品妹子,拒絕道:對不起,我已經不相信愛情了
。此時,有個聲音在輕聲的嘀咕着:你還能夠相信的!
。
好的,那麼,我就再讓你相信一次。首先添加文件 src/mixins/func.js
:
export default {
mounted() {
this.sayHello();
},
methods: {
sayHello() {
console.log('產品妹子,你好美!');
},
},
};
複製代碼
而後在須要的組件中引入就好了:
import logMixin from '@/mixins/log';
import funcMixin from '@/mixins/func';
export default {
name: 'comp1',
mixins: [logMixin, funcMixin],
// ...
}
複製代碼
運行項目,打開控制檯輸出以下:
Component comp1 created.
Component comp2 created.
Component comp1 mounted.
產品妹子,你好美!
Component comp2 mounted.
複製代碼
好了,你終於能夠跟產品妹子一塊兒在夕陽下愉快地奔跑了。忽然有一天,組件渲染後打點,成了公司的規範
,也就是你編寫的全部組件都須要打點了,產品妹子很無奈的看着你說:這不是我想要的結果,是你作的太優秀,被公司提上了日程,寫入了編碼規範
.....可現實就是這樣,你總想逃,卻逃不掉
......
其實你還能夠逃的,Vue.mixin 說。
全局註冊一個混入,影響註冊以後全部建立的每一個 Vue 實例。插件做者可使用混入,向組件注入自定義的行爲。不推薦在應用代碼中使用。
這不就是你一直追尋的愛情嗎?因而你移除了以前引入的 logMixin
,而後默默地在入口文件(src/main.js
)中寫下了愛情的宣言:
//...
Vue.mixin({
created() {
console.log(`Component ${this.$options.name} created from 全局打點`);
},
mounted() {
console.log(`Component ${this.$options.name} mounted from 全局打點`);
},
});
// new Vue....
複製代碼
運行項目,打開控制檯輸出以下:
Component undefined created from 全局打點
Component App created from 全局打點
Component Index created from 全局打點
Component router-link created from 全局打點
Component comp1 created from 全局打點
Component comp1 created.
Component comp2 created from 全局打點
Component comp2 created.
Component comp3 created from 全局打點
Component router-link mounted from 全局打點
Component comp1 mounted from 全局打點
Component comp1 mounted.
產品妹子,你好美!
Component comp2 mounted from 全局打點
Component comp2 mounted.
Component comp3 mounted from 全局打點
Component Index mounted from 全局打點
Component App mounted from 全局打點
Component undefined mounted from 全局打點
複製代碼
你會發現全部的 Vue 組件都注入了打點。
其實 mixins
用起來很是簡單,可是其背後的原理,仍是值得咱們去深究的:
mixins
後,鉤子函數是依次執行的,而不是替換?mixins
後,自身 data
屬性優於混入屬性?要想回答上面的問題,咱們得從 vue 源碼開始提及。
Vue 在初始化 mixin
的時候,對於不一樣的屬性,採用的策略是不一樣的,初始化代碼在文件 src/core/global-api.js 中, 以下:
import { mergeOptions } from '../util/index'
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
Vue.options = mergeOptions(Vue.options, mixin)
}
}
複製代碼
你會發現是經過 mergeOptions
函數來進行合併的,它在文件 src/core/util/options.js, 它的源碼以下:
/** * Merge two option objects into a new one. * Core utility used in both instantiation and inheritance. */
export function mergeOptions ( parent: Object, child: Object, vm?: Component ): Object {
// 省略沒必要要代碼
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
}
複製代碼
這個函數很好理解,大概作的事情就是將 child
的屬性合入到 parent
中,不一樣屬性採用了不一樣的策略,這些策略都定義在 strats
對象上。
咱們先看看 生命週期函數
的合併策略,代碼以下:
/** * Hooks and param attributes are merged as arrays. */
function mergeHook ( parentVal: ?Array<Function>, childVal: ?Function | ?Array<Function> ): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
複製代碼
能夠發現 Vue 實例的生命週期函數最終都賦值成了一個數組,並對 mixins
中的進行了數組合並。這就是爲何組件 mixins
後的生命週期函數是依次執行的緣由。
一樣再來看看 data
的合入策略:
/** * Helper that recursively merges two data objects together. */
function mergeData (to: Object, from: ?Object): Object {
let key, toVal, fromVal
for (key in from) {
toVal = to[key]
fromVal = from[key]
if (!hasOwn(to, key)) {
set(to, key, fromVal)
} else if (isObject(toVal) && isObject(fromVal)) {
mergeData(toVal, fromVal)
}
}
return to
}
複製代碼
這個過程就是對象屬性的合併,可是 to
上的優先級是高於 from
的,這就是爲何咱們在對一個組件進行 mixins
的時候,自身 data
優先級高於混入的 data
屬性,也就是若是 mixins
中和自身均含有相同屬性時,混入屬性值不會被添加到當前組件中。
感興趣的同窗,還能夠去研究下其餘屬性的混入策略,源碼均在 src/core/util/options.js 中,也很好理解。
越是簡單的東西,越是把雙刃劍,實際使用中必定要注意,特別是全局性的混入,這會帶來性能開銷。你們能夠多編寫,多總結,找到最合適的使用習慣就好,建議多閱讀著名開源項目的源碼,你會從中學到更多前輩們的技巧。