Vue2.0學習筆記:Vue中的Mixins
在專案開發的時候,常會碰到這樣的一種現象:有兩個元件非常相似,比如較為熟悉的 Modal
、 Tooltip
和 Popover
,它們都具有同樣的基本函式,而且它們之前也有足夠的不同。很多時候,就讓人很難做出選擇:是把它們拆會成多個不同的元件呢?還是隻使用一個元件,建立足夠的屬性來改變不同的情況。
這些解決方案都不夠完美。如果拆會成多個元件,就不得不冒著如果功能變動你要在多個檔案中更新它的風險。另一方面,太多的屬笥會很快變得混亂,難維護,甚至對於元件開發者自已面言,也是件難事。
在Vue中,對於這樣的場景,官方提供了一種叫混入( mixins
)的特性。使用 mixins
允許你封裝一塊在應用的其他元件中都可以使用的函式。如果被正確的使用,他們不會改變函式作用域外部的任何東西,所以多次執行,只要是同樣的輸入,總是能得到一樣的值。
既然Vue的 mixins
這麼優秀,那應該怎麼使用呢?今天我們的目的就是學習如何在Vue的專案中使用 mixins
。
什麼是Mixins
Vue的官方文件是這樣描述 mixins
的:
mixins
是一種分發Vue元件中可複用功能的一種靈活方式。
mixins
是一個JavaScript物件,可以包含元件中的任意選項,比如Vue例項中生命週期的各個鉤子函式,也可以是 data
、 components
、 methods
或 directives
等。在Vue中, mixins
為我們提供了在Vue元件中共用功能的方法。使用方式很簡單,將共用的功能以物件的方式傳入 mixins
選項中。當元件使用 mixins
物件時,所有 mixins
物件的選項都將被混入該元件本身的選項。
如何建立Mixins
在Vue中,建立 mixins
方法很簡單。簡單一點的,在Vue元件中,我們將 mixins
物件指定為:
const myMixin = { created () { console.log(`來自Mixins中的訊息`) } } let app = new Vue({ el: '#app', mixins: [myMixin] })
上面看到的方式是在元件中宣告一個 mixins
物件。如果元件多起來,這樣的方式,感覺還是沒有解決我們文章提到的需求一樣。更好的做法,我們可以像下面這樣做。比如在專案的 src
下面建立一個存放 mixins
的目錄,比如一個叫 mixin
目錄。然後這個目錄中建立你想要的 .js
檔案,比如這裡建立了一個叫 mixins.js
:
為了方便演示,把上面建立的 mixins
相關的程式碼移入 mixins.js
檔案中:
// src/mixins/mixins.js export const myMixin = { created() { console.log(`來自Mixins中的訊息`); } };
在需要使用該 mixins
的元件中先引入 mixins.js
檔案,接著在元件中的 mixins
選擇中呼叫在 mixins.js
建立的 mixins
,比如:
// HelloWorld.vue <script> import { myMixin, myMixin2 } from "../mixins/mixins.js"; export default { name: "HelloWorld", data() { return { msg: "From Vue Component" }; }, mixins: [myMixin] }; </script>
這種方式和前面使用 mixins
方式最終得到的結果是一樣的。
使用 mixins
,在 mixins
中指定的任何選項都會立即與Vue元件的選項合併。可以在元件的 mixins
陣列中指定多個 mixin
。一旦指定了,所有的 mixin
選項都會被匯入到元件中。比如:
// mixins.js export const mixObj = { created() { this.hello() }, methods: { hello() { console.log(`Hello from Mixin`) } } } export const minObjAnother = { created () { this.prompt() }, methods: { prompt() { console.log(`Prompt from Mixin Another`) }, hello() { console.log(`Hello from Mixin Another`) } } }
如果在元件中要同時呼叫 mixObj
和 minObjAnother
兩個 mixins
時,需要在元件的 mixins
選項的值新增這兩個 mixins
,比如:
<!-- HelloWorld.vue --> <script> import { mixObj, minObjAnother } from "../mixins/mixins.js"; export default { name: "HelloWorld", mixins: [mixObj, minObjAnother], }; </script>
此時在 console
中輸出的結果如下:
從輸出的結果中我們可以看出, mixObjAnother
中的 hello()
方法取替了 mixObj
中的 hello()
方法。
是不是感到疑惑。如果是,請繼續往下閱讀。
合併選項和衝突
當元件和混入物件含有同名選項時,這些選項將以恰當的方式混合。
什麼是“ 恰當的方式 ”?對於我這樣的初學者而言,有點難於理解。上例中我們看到了,每個 mixins
物件中都具有生命週期的鉤子(Vue生命週期)和方法(甚至其他的),而且這些東西都是可用的。另外在Vue中的元件,同樣也有這些生命週期的鉤子函式和方法。所以當在元件上註冊重複的過程時,順序顯得尤其重要。
預設是 mixins
上會首先被註冊,元件上的接著註冊!
這樣的做主要是因為必要時可以重寫。
元件擁有最終發言權: 當有一個衝突並且這個元件不得不決定哪個勝出的時候,這真的變得很重要,否則,所有東西都被放在一個數組中執行, mixins
中的先執行,元件中的接著執行。
在Vue中 mixins
常見的合併主要有三種情形:資料 data
、生命週期中的鉤子函式和值為物件的選項。接下來,咱們分別來看看這三種情形:
資料合併
資料物件在內部分進行淺合併(一層屬性深度),在和元件的資料發生衝突時, 以元件資料優先 。
// mixins.js export const mixObj = { data() { return { msg: "Message from Mixin Object" }; } }; export const minObjAnother = { data() { return { msg: "Message from Mixin Object Another", name: "Foo" }; } }; <!-- HelloWorld.vue --> <script> import { mixObj, minObjAnother } from "../mixins/mixins.js"; export default { name: "HelloWorld", data() { return { msg: "Message from Vue Component", name: 'Bar', age: 20 }; }, mixins: [mixObj, minObjAnother], created () { console.log(this.$data) } }; </script>
最終的結果是, HelloWorld
元件中的 msg
和 name
取替了 mixins
中的,另外 age
欄位被合併進行。
鉤子函式合併
當元件使用的 mixins物件有相同的選項時,比如鉤子函式,就會全部被合併到一個數組中,因此都會被執行,並且
mixins`物件中的鉤子會先被執行。
const myMixin = { created () { console.log(`來自Mixins中的訊息`) } } new Vue({ el: '#app', mixins: [myMixin], created () { console.log(`來自元件中的訊息`) } })
值為物件選項合併
當 mixins
物件和元件中的選項的值為物件時,比如 methods
、 components
和 directives
,將被混合為同一個物件。當兩個物件鍵名衝突時, 元件選項優先 。
const myMixin = { data () { return { msg: 'From Vue Mixins' } }, created() { console.log(`來自Mixins的訊息`) }, methods: { message () { console.log(this.msg) } } }; export default { name: "HelloWorld", data() { return { msg: "From Vue Component" }; }, mixins: [myMixin], created() { console.log(`來自元件的訊息`) }, methods: { message () { console.log(this.msg) } } };
全域性Mixins
當我們使用全域性混合時,我們不是指能夠在每個元件上訪問它們,就像是過濾器一樣。我們能夠通過 mixins:[myMixins]
訪問元件上的混合物件。
全域性混合被註冊到了每個單一元件上。因此,它們的使用場景極其有限並且要非常的小心。一個我能想到的用途就是它像一個外掛,你需要賦予它訪問所有東西的許可權。但即使在這種情況下,我也對你正在做的保持警惕,尤其是你在應用中擴充套件的函式,可能對你來說是不可知的。
為了建立一個全域性例項,我們可以把它放在 Vue 例項之上。在一個典型的 Vue-cli 初始化的專案中,它可能在你的 main.js
檔案中。
Vue.mixin({ mounted() { console.log('hello from mixin!') } }) new Vue({ ... })
謹慎使用全域性混入物件,因為會影響到每個單獨建立的 Vue 例項 (包括第三方模板)。大多數情況下,只應當應用於自定義選項,就像上面示例一樣。也可以將其用作 ofollow,noindex" target="_blank">Plugins 以避免產生重複應用。
使用Mixins例項
經過上面的介紹,我們對Vue中的 mixins
應該有了一定的瞭解。接下來通過簡單的例項來向大家闡述在專案中怎麼使用上面介紹的 mixins
。
先來看一個簡單的示例,也就是文章中提到的 Modal
和 Tooltip
元件。雖然這兩個元件是不同的元件,但實際上它們的作用是類似的: 切換一個狀態布林值 。換句話說,這兩個元件表面上看起來不一樣,用法不一樣,但邏輯是一樣的。
// 模態框 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 } }
我們可以提取出這個邏輯並建立可以被重用的項:
// toggle.js export const toggle = { data() { return { isShowing: false }; }, methods: { toggleShow() { this.isShowing = !this.isShowing; } } }; <!-- Modal.vue --> <template> <div> <h3>Let's trigger this here modal!</h3> <button @click="toggleShow"> <span v-if="isShowing">Hide child</span> <span v-else>Show child</span> </button> <Child v-if="isShowing" class="modal"> <button @click="toggleShow">Close</button> </Child> </div> </template> <script> import Child from "./Child"; import { toggle } from "../mixins/toggle.js"; export default { name: "Modal", mixins: [toggle], components: { Child } }; </script> <!-- ToolTip.vue --> <template> <div class="tooltip-demo"> <h3 @click="toggleShow"> <span v-if="isShowing">Click Me Again</span> <span v-else>Click Me Please</span> </h3> <Child v-if="isShowing" class="tooltip"> <p>I'm a tooltip this time</p> </Child> </div> </template> <script> import { toggle } from '../mixins/toggle.js' import Child from './Child' export default { name: 'ToolTip', mixins: [toggle], components: { Child } } </script>