詳解Vue元件複用和擴充套件之道
概述
軟體程式設計有一個重要的原則是 D.R.Y(Don't Repeat Yourself),講的是儘量複用程式碼和邏輯,減少重複。元件擴充套件可以避免重複程式碼,更易於快速開發和維護。那麼,擴充套件 vue 元件的最佳方法是什麼?
Vue 提供了不少www.cppcns.com API 和模式來支援元件複用和擴充套件,你可以根據自己的目的和偏好來選擇。
本文介紹幾種比較常見的方法和模式,希望對你有所幫助。
擴充套件元件是否必要
要知道,所有的元件擴充套件方法都會增加複雜性和額外程式碼,有時候還會增加效能消耗。
因此,在決定擴充套件元件之前,最好先看看有沒有其他更簡單的設計模式能完成目標。
下面幾種元件設計模式通常足夠替代擴充套件元件了:
- props 配合模板邏輯
- slot 插槽
- javascript 工具函式
props 配合模板邏輯
最簡單的方法是通過props結合模板條件渲染,來實現元件的多功能。
比如通過 type 屬性:
MyVersatileComponent.vue
<template> <div class="wrapper"> <div v-if="type === 'a'">...</div> <div v-else-if="type === 'b'">...</div> <!--etc etc--> </div> </template> <script> export default { props: { type: String },... } </script>
使用元件的時候傳不同的type值就能實現不同的結果。
// *ParentComponent.vue* <template> <MyVersatileComponent type="a" /> <MyVersatileComponent type="b" /> </template>
如果出現下面兩種情況,就說明這種模式不適用了,或者用法不對:
- 元件組合模式把狀態和邏輯分解成原子部分,從而讓應用具備可擴充套件性。如果元件記憶體在大量條件判斷,可讀性和可維護性就會變差。
- props 和模板邏輯的本意是讓元件動態化,但是也存在執行時資源消耗。如果你利用這種機制在執行時解決程式碼組合問題,那是一種反模式。
slot(插槽)
另一種可避免元件擴充套件的方式是利用 slots(插槽),就是讓父元件在子元件內設定自定義內容。
// *MyVersatileComponent.vue* <template> <div class="wrapper"> <h3>Common markup</div> <slot /> </div> </template>
// *ParentComponent.vue* <template> <MyVersatileComponent> <h4>Inserting into the slot</h4> </MyVersatileComponent> </template>
渲染結果:
<div class="wrapper"> <h3>Common markup</div> <h4>Inserting into the slot</h4> </div>
這種模式有一個潛在約束, slot 內的元素從屬於父元件的上下文,在拆分邏輯和狀態時可能不太自然。scoped slot會更靈活,後面會在無渲染元件一節裡提到。
javaScript 工具函式
如果只需要在各元件之間複用獨立的函式,那麼只需要抽取這些 JavaScript 模組就行了,根本不需要用到元件擴充套件模式。
JavaScript 的模組系統是一種非常靈活和健壯的程式碼共享方式,所以你應該儘可能地依靠它。
MyUtilityFunction.js
export default function () { ... }
MyComponent.vue
import MyUtilityFunction from "./MyUtilityFunction"; export default { methods: { MyUtilityFunction } }
擴充套件元件的幾種模式
如果你已經考慮過以上幾種簡單的模式,但這些模式還不夠靈活,無法滿足需求。那麼就可以考慮擴充套件元件了。
擴充套件 Vue 元件最流行的方法有以下四種:
- Composition 函式
- mixin
- 高階元件(HOC)
- 無渲染元件
每一種方法都有其優缺點,根據使用場景,或多或少都有適用的部分。
Composition API
元件之間共享狀態和邏輯的最新方案是 Composition API。這是 Vue 3 推出的 API,也可以在 Vue 2 裡當外掛使用。
跟之前在元件定義配置物件裡宣告data,computed,methods等屬性的方式不同,Composition API 通過一個 setup 函式宣告和返回這些配置。
比如,用 Vue 2 配置屬性的方式宣告 Counter 元件是這樣的:
Counter.vue
<template>
<button @click="increment">
Count is: {{ count }},double is: {{ double }}
</button>
<template>
<script>
export程式設計客棧 default {
data: () => ({
count: 0
}),methods: {
increment() {
this.count++;
}
},computed: {
double () {
return this.count * 2;
}
}
}
</script>
用 Composition API 重構這個元件,功能完全一樣:
Counter.vue
<template><!--as above--><template> <script> import { reactive,computed } from "vue"; export default { setup() { const state = reactive({ count: 0,double: computed(() => state.count * 2) }); function increment() { state.count++ } return { count,double,increment } } } </script>
用 Composition API 宣告元件的主要好處之一是,邏輯複用和抽取變得非常輕鬆。dkWKnYN
進一步重構,把計數器的功能移到 JavaScript 模組 useCounter.js中:
useCounter.js
import { reactive,computed } from "vue"; export default function { const state = reactive({ count: 0,double: computed(() => state.count * 2) }); function increment() { state.count++ } return { count,increment } }
現在,計數器功能可以通過setup函式無縫引入到任意 Vue 元件中:
MyComponent.vue
<template><!--as above--></template> <script> import useCounter from "./useCounter"; export default { setup() { const { count,increment } = useCounter(); return { count,increment } } } </script>
Composition 函式讓功能模組化、可重用,是擴充套件元件最直接和低成本的方式。
Composition API 的缺點
Composition API 的缺點其實不算什麼——可能就是看起來有點囉嗦,並且新的用法對一些 Vue 開發者來說有點陌生。
關於 Composition API 優缺點的討論,推薦閱讀:When To Use The New Vue Composition API (And When Not To)
mixin
如果你還在用 Vue 2,或者只是喜歡用配置物件的方式定義元件功能,可以用 mixin 模式。mixin 把公共邏輯和狀態抽取到單獨的物件,跟使用 mixin 的元件內部定義物件合併。
我們程式設計客棧繼續用之前的Counter元件例子,把公共邏輯和狀態放到CounterMixin.js模組中。
CounterMixin.js
export default { data: () => ({ count: 0 }),computed: { double () { return this.count * 2; } } }
使用 mixin 也很簡單,只要匯入對應模組並在mixins數組裡加上變數就行。元件初始化時會把 mixin 物件與元件內部定義物件合併。
MyComponent.vue
import CounterMixin from "./CounterMixin"; export default { mixins: [CounterMixin],methods: { decrement() { this.count--; } } }
選項合併
如果元件內的選項跟 mixin 衝突怎麼辦?
比如,給元件定義一個自帶的increwww.cppcns.comment方法,哪個優先順序更高呢?
MyComponent.vue
import CounterMixin from "./CounterMixin"; export default { mixins: [CounterMixin],methods: { // 自帶的 `increment`` 方法會覆蓋 mixin 的`increment` 嗎? increment() { ... } } }
這個時候就要說到 Vue 的合併策略了。Vue 有一系列的規則,決定了如何處理同名選項。
通常,元件自帶的選項會覆蓋來自 mixin 的選項。但也有例外,比如同型別的生命週期鉤子,不是直接覆蓋,而是都放進陣列,按順序執行。
你也可以通過 自定義合併策略 改變預設行為。
mixin 的缺點
作為擴充套件元件的一種模式,mixin 對於簡單的場景還算好用,一旦規模擴大,問題就來了。不僅需要注意命名衝突問題(尤其是第三方 mixin),使用了多個 mixin 的元件,很難搞清楚某個功能到底來自於哪裡,定位問題也比較困難。
高階元件
高階元件(HOC)是從 React 借用的概念,Vue 也能使用。
為了理解這個概念,我們先拋開元件,看看兩個簡單的 JavaScript 函式,increment和 double。
function increment(x) { return x++; } function double(x) { return x * 2; }
假設我們想給這兩個函式都加一個功能:在控制檯輸出結果。
為此,我們可以用高階函式模式,新建一個addLogging函式,接受函式作為引數,並返回一個帶有新增功能的函式。
function addLogging(fn) { return function(x) { const result = fn(x); console.log("The result is: ",result); return result; }; } const incrementWithLogging = addLogging(increment); const doubleWithLogging = addLogging(double);
元件如何利用這種模式呢?類似地,我們建立一個高階元件來渲染Counter元件,同時新增一個decrement方法作為例項屬性。
實際程式碼比較複雜,這裡只給出虛擬碼作為示意:
import Counter from "./Counter"; // 虛擬碼 const CounterWithDecrement => ({ render(createElement) { const options = { decrement() { this.count--; } } return createElement(Counter,options); } });
HOC 模式比 mixin 更簡潔,擴充套件性更好,但是代價是增加了一個包裹元件,實現起來也需要技巧。
無渲染元件
如果需要在多個元件上使用相同的邏輯和狀態,只是展示方式不同,那麼就可以考慮無渲染元件模式。
該模式需要用到兩類元件:邏輯元件用於宣告邏輯和狀態,展示元件用於展示資料。
邏輯元件
還是回到Counter的例子,假設我們需要在多個地方重用這個元件,但是展示方式不同。
建立一個CounterRenderless.js用於定義邏輯元件,包含邏輯和狀態,但是不包含模板,而是通過 render函式宣告 scoped slot。
scoped slot暴露三個屬性給父元件使用:狀態count,方法increment 和計算屬性 double。
CounterRenderless.js
export default { data: () => ({ count: 0 }),computed: { double () { return this.count * 2; } },render() { return this.$scopedSlots.default({ count: this.count,double: this.double,increment: this.toggleState,}) } }
這裡的scoped slot是這種模式裡邏輯元件的關鍵所在。
展示元件
接下來是展示元件,作為無渲染元件的使用方,提供具體的展示方式。
所有的元素標籤都包含在scoped slot裡。可以看到,這些屬性在使用上跟模板直接放在邏輯元件裡沒什麼兩樣。
CounterWithButton.vue
<template> <counter-renderless slot-scope="{ count,increment }"> <div>Count is: {{ count }}</div> <div>Double is: {{ double }}</div> <button @click="increment">Increment</button> </counter-renderless> </template> <script> import CounterRenderless from "./CountRenderless"; export default { components: { CounterRenderless } } </script>
無渲染元件模式非常靈活,也容易理解。但是,它沒有前面那幾種方法那麼通用,可能只有一種應用場景,那就是用於開發元件庫。
模板擴充套件
上面的 API 也好,設計模式也罷,都有一種侷限性,就是無法擴充套件元件的模板。Vue 在邏輯和狀態方面有辦法重用,但是對於模板標籤就無能為力了。
有一種比較 hack 的方式,就是利用 HTML 前處理器,比如 Pug,來處理模板擴充套件。
第一步是建立一個基礎模板.pug檔案,包含公共的頁面元素。還要包含一個block input,作為模板擴充套件的佔位符。
BaseTemplate.pug
div.wrapper h3 {{ myCommonProp }} <!--common markup--> block input <!--extended markup outlet -->
為了能擴充套件這個模板,需要安裝 Vue Loader 的 Pug 外掛。然後就可以引入基礎模板並利用block input語法替換佔位部分了:
MyComponent.vue
<template lang="pug"> extends BaseTemplate.pug block input h4 {{ myLocalProp }} <!--gets included in the base template--> </template>
一開始你可能會認為它跟 slot 的概念是一樣的,但是有個區別,這裡的基礎模板不屬於任何單獨的元件。它在編譯時跟當前元件合併,而不是像 slot 那樣是在執行時替換。
以上就是詳解Vue元件複用和擴充套件之道的詳細內容,更多關於Vue元件複用和擴充套件的資料請關注我們其它相關文章!