有一種很常見的情況:有兩個非常相似的元件,他們的基本功能是一樣的,但他們之間又存在著足夠的差異性,此時的你就像是來到了一個分岔路口:我是把它拆分成兩個不同的元件呢?還是保留為一個元件,然後通過props傳值來創造差異性從而進行區分呢?
兩種解決方案都不夠完美:如果拆分成兩個元件,你就不得不冒著一旦功能變動就要在兩個檔案中更新程式碼的風險,這違背了 DRY 原則。反之,太多的props傳值會很快變得混亂不堪,從而迫使維護者(即便這個人是你)在使用元件的時候必須理解一大段的上下文,拖慢寫碼速度。
使用Mixin。Vue 中的Mixin對編寫函式式風格的程式碼很有用,因為函數語言程式設計就是通過減少移動的部分讓程式碼更好理解(引自 Michael Feathers )。Mixin允許你封裝一塊在應用的其他元件中都可以使用的函式。如果使用姿勢得當,他們不會改變函式作用域外部的任何東西,因此哪怕執行多次,只要是同樣的輸入你總是能得到一樣的值,真的很強大!
基礎例項
我們有一對不同的元件,它們的作用是通過切換狀態(Boolean型別)來展示或者隱藏模態框或提示框。這些提示框和模態框除了功能相似以外,沒有其他共同點:它們看起來不一樣,用法不一樣,但是邏輯一樣。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// 模態框 const Modal = { template: '#modal', data() { return { isShowing: false } }, methods: { toggleShow() { this.isShowing = !this.isShowing; } }, components: { appChild: Child } } // 提示框 const Tooltip = { template: '#tooltip', data() { return { isShowing: false } }, methods: { toggleShow() { this.isShowing = !this.isShowing; } }, components: { appChild: Child } } |
我們可以在這裡提取邏輯並建立可以被重用的項:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
const toggle = { data() { return { isShowing: false } }, methods: { toggleShow() { this.isShowing = !this.isShowing; } } } const Modal = { template: '#modal', mixins: [toggle], components: { appChild: Child } }; const Tooltip = { template: '#tooltip', mixins: [toggle], components: { appChild: Child } }; |
你可以點選這裡,檢視 Sarah Drasner(@sdras) 在CodePen上編寫 Mixin 的例子
為了更容易理解Mixin,這個例子故意編寫得簡單一些。真實應用中Mixin有如下的應用,但是它的作用也不僅限於此:獲取視窗和元件的尺寸,採集特定的滑鼠事件和圖表的基本元素。Paul Pflugradt 有一個關於 Vue Mixins 的優秀專案,值得一提的是它是用 coffeescript 編寫的。
用法
上面這個codepen的例子並沒有告訴我們在一個真實的應用中如何使用Mixin,所以我們看看下面的這個。
你可以按照你喜歡的任意方式設定你的目錄結構,但為了結構規整我喜歡新建一個mixin
目錄。我們建立的這個檔案含有.js
副檔名(跟.vue
相對,就像我們的其他檔案),為了使用Mixin我們需要輸出一個物件。
接著我們可以在Modal.vue使用這樣的寫法,來引入這個Mixin:
1 2 3 4 5 6 7 8 9 10 |
import Child from './Child' import { toggle } from './mixins/toggle' export default { name: 'modal', mixins: [toggle], components: { appChild: Child } } |
即便我們使用的是一個物件而不是一個元件,生命週期函式對我們來說仍然是可用的,理解這點很重要。我們也可以這裡使用mounted()
鉤子函式,它將被應用於元件的生命週期上。這種工作方式真的很靈活也很強大。
合併
在下面的這個例子,我們可以看到,我們不僅僅是實現了自己想要的功能,並且Mixin中的生命週期的鉤子也同樣是可用的。因此,當我們在元件上應用Mixin的時候,有可能元件與Mixin中都定義了相同的生命週期鉤子,這時候鉤子的執行順序的問題凸顯了出來。預設Mixin上會首先被註冊,元件上的接著註冊,這樣我們就可以在元件中按需要重寫Mixin中的語句。元件擁有最終發言權。當發生衝突並且這個元件就不得不“決定”哪個勝出的時候,這一點就顯得特別重要,否則,所有的東西都被放在一個陣列當中執行,Mixin將要被先推入陣列,其次才是元件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//mixin const hi = { mounted() { console.log('hello from mixin!') } } //vue instance or component new Vue({ el: '#app', mixins: [hi], mounted() { console.log('hello from Vue instance!') } }); //Output in console > hello from mixin! > hello from Vue instance! |
如果這兩個衝突了,我們看看 Vue例項或元件是如何決定輸贏的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//mixin const hi = { methods: { sayHello: function() { console.log('hello from mixin!') } }, mounted() { this.sayHello() } } //vue instance or component new Vue({ el: '#app', mixins: [hi], methods: { sayHello: function() { console.log('hello from Vue instance!') } }, mounted() { this.sayHello() } }) // Output in console > hello from Vue instance! > hello from Vue instance! |
你可能已經注意到這有兩個console.log
而不是一個——這是因為第一個函式被呼叫時,沒有被銷燬,它只是被重寫了。我們在這裡呼叫了兩次sayHello()
函式。
全域性Mixin
當我們使用“全域性”來描述Mixin的時候,我們並不是說Mixin能夠像filter,在每個元件都能被訪問到。只是我們能夠在元件通過mixins:
訪問元件上的Mixin物件。
全域性Mixin被註冊到了每個單一元件上。因此,它們的使用場景極其有限並且在使用的時候我們需要非常小心。一個我能想到的用途就是類似於外掛,你需要賦予它訪問所有東西的許可權。但即使在這種情況下,我也對你正在做事情的充滿警惕,尤其當你打算為應用增加通能的時候,這樣做可能對你來說是個潘多拉的盒子。
為了建立一個全域性例項,我們可以把它放在Vue例項之上。在一個典型的 Vue-cli 初始化的專案中,它可能在你的main.js
檔案中。
1 2 3 4 5 6 7 8 9 |
Vue.mixin({ mounted() { console.log('hello from mixin!') } }) new Vue({ ... }) |
再次提醒,小心使用它!那個console.log
將會出現在每個元件上,在這個案例裡還不算壞(除了控制檯上有多餘的輸出)。但如果全域性Mixin被錯誤的使用,你將能看到它有多可怕。
結論
Mixin對於封裝一小段想要複用的程式碼來講是有用的。對你來說Mixin當然不是唯一可行的選擇:比如說高階元件就允許你組合相似函式,Mixin只是的一種實現方式。我喜歡Mixin,因為我不需要傳遞狀態,但是這種模式當然也可能會被濫用,所以,仔細思考下哪種選擇對你的應用最有意義吧!