4種方案帶你探索Vue代碼復(fù)用的前世今生
前言
在我們平時(shí)開發(fā)中,不論你使用什么語言,當(dāng)遇到了大量的重復(fù)代碼,我們可能會(huì)去將重復(fù)代碼提取出來,獨(dú)立一個(gè)模塊,在多個(gè)地方引用,這是一個(gè)好習(xí)慣,是值得推薦的!當(dāng)然也有些同學(xué)不感冒,使用到了直接CV,撇開代碼規(guī)范,設(shè)計(jì)模式這些不談,往往CV會(huì)給你帶來更大的工作量(比如用了很多地方,你要去CV很多地方,如果后續(xù)有變動(dòng),你又要重復(fù)CV到很多地方......,當(dāng)然不推薦CV)。我們所熟知的Vue.js也在如何提取公共代碼復(fù)用方面也一直在探索優(yōu)化,本文筆者就來和各位聊聊Vue.js代碼復(fù)用的前世今生。
在Vue.js中我們可通過以下4種方案來實(shí)現(xiàn)代碼邏輯復(fù)用:
- mixin
- 高階組件
- 作用域插槽(scoped slots)
- Composition API 組合式函數(shù)
可能各位常用的是mixin,沒關(guān)系,其他幾種也很好理解。筆者會(huì)通過一個(gè)實(shí)際的案例分別使用以上的方案實(shí)現(xiàn),并分析各種方案的優(yōu)缺點(diǎn)來帶各位掘友體會(huì)Vue.js在代碼邏輯復(fù)用方面的優(yōu)化歷程。
案例:就以大家所熟知的 鼠標(biāo)位置 來吧
Vue.js 代碼邏輯復(fù)用
我們先不考慮復(fù)用,先來看看如何實(shí)現(xiàn)鼠標(biāo)位置這個(gè)功能,功能十分簡單,大家肯定都會(huì),筆者就不廢話了,直接看下代碼吧:
基礎(chǔ)實(shí)現(xiàn)
<script src="https://unpkg.com/vue@next"></script>
<div id="app"></div>
<script>
const { createApp } = Vue
const App = {
template: `{{x}} {{y}}`,
data() {
return {
x: 0,
y: 0
}
},
methods: {
handleMouseMove(e) {
this.x = e.pageX
this.y = e.pageY
}
},
mounted() {
window.addEventListener('mousemove', this.handleMouseMove)
},
unmounted() {
window.removeEventListener('mousemove', this.handleMouseMove)
}
}
createApp(App).mount('#app')
</script>效果:

接下來,我們嘗試將這個(gè)功能提取以達(dá)到復(fù)用的目的,先來看看 mixin 這個(gè)方案。
mixin
簡單來說,mixin允許我們提供一個(gè)或多個(gè)像普通實(shí)例對象一樣包含實(shí)例選項(xiàng)的對象,Vue.js會(huì)以一定的邏輯自動(dòng)合并這些對象里面的選項(xiàng)和組件的選項(xiàng)。舉例來說,如果你的 mixin 包含了一個(gè) created 鉤子,而組件自身也有一個(gè),那么這兩個(gè)函數(shù)都會(huì)被調(diào)用。本文不再贅述,請參考Vue.js——mixins。以下就是通過mixin實(shí)現(xiàn)復(fù)用MouseMove的邏輯:
<script>
const { createApp } = Vue
const MouseMoveMixin = {
data() {
return {
x: 0,
y: 0
}
},
methods: {
handleMouseMove(e) {
this.x = e.pageX
this.y = e.pageY
}
},
mounted() {
window.addEventListener('mousemove', this.handleMouseMove)
},
unmounted() {
window.removeEventListener('mousemove', this.handleMouseMove)
}
}
const App = {
template: `{{x}} {{y}}`,
mixins: [ MouseMoveMixin ]
}
createApp(App).mount('#app')
</script>效果與之前的一致。
我們來分析下mixin的缺點(diǎn):
- 當(dāng)我們的組件有多個(gè)
mixin,比如:mixins: [ MouseMoveMixin, anthorMixin, fooMixin ],我們就會(huì)分不清哪些變量是從MouseMoveMixin來的?哪些變量是從anthorMixin來的?那就出現(xiàn)了第一個(gè)缺點(diǎn):變量來源不清 - 同樣的,當(dāng)我們的組件有多個(gè)
mixin,我們不得不去考慮他們注入的變量名會(huì)不會(huì)存在沖突。那就出現(xiàn)了第二個(gè)缺點(diǎn):命名沖突
高階組件
所謂高階組件,就是通過實(shí)現(xiàn)一個(gè)包裝函數(shù),這個(gè)包裝函數(shù)返回像普通實(shí)例對象一樣包含實(shí)例選項(xiàng)的對象,該對象內(nèi)包含render選項(xiàng),render用于渲染內(nèi)部的組件,并將屬性通過props注入到內(nèi)部組件。比如我們可以像下面這樣通過高階組件復(fù)用這個(gè)鼠標(biāo)位置的邏輯。
<script>
const { createApp, h } = Vue
// 包裝函數(shù)
function withMouse(inner) {
return {
data() {
return {
x: 0,
y: 0
}
},
methods: {
handleMouseMove(e) {
this.x = e.pageX
this.y = e.pageY
}
},
mounted() {
window.addEventListener('mousemove', this.handleMouseMove)
},
unmounted() {
window.removeEventListener('mousemove', this.handleMouseMove)
},
render() {
// 注入 x, y
return h(inner, { x: this.x, y: this.y })
}
}
}
const App = withMouse({
template: `{{x}} {{y}}`,
props: ['x', 'y']
})
createApp(App).mount('#app')
</script>我們再來分析下,用高階組件來實(shí)現(xiàn)邏輯復(fù)用,是不是就沒有缺點(diǎn)呢?
同樣的,我們還是假設(shè)我有還多塊邏輯要復(fù)用,比如把mixins: [ MouseMoveMixin, anthorMixin, fooMixin ]改寫成高階組件,那將變成以下代碼:
function withMouse(inner) {
// 此處省略
}
function withFoo(inner) {
// 此處省略
}
function withAnthor(inner) {
// 此處省略
}
const App = withAnthor(withFoo(withMouse({
template: `{{x}} {{y}}`,
props: ['x', 'y', 'foo', 'anthor']
})))
createApp(App).mount('#app')mixin的問題它都有,props中我們依然看不清哪些屬性是由哪個(gè)高階組件注入的,也依然不得不考慮命名沖突的問題。(有些同學(xué)可能覺得,如果注入的變量名能夠和包裹函數(shù)名有聯(lián)系,那就能夠看出來。那確實(shí)是的,但是這就需要有很嚴(yán)格的開發(fā)規(guī)范和代碼走查來約束開發(fā)人員了)顯然高階組件也不是什么”靈丹妙藥“,我們接著看如何使用scoped slots來實(shí)現(xiàn)這個(gè)邏輯復(fù)用。
作用域插槽(scoped slots)
作用域插槽(scoped slots)這種方式和高階組件有點(diǎn)像,區(qū)別在于不是通過函數(shù)來包裹,而是通過實(shí)現(xiàn)一個(gè)組件來包裹,我們叫它父組件,在父組件實(shí)現(xiàn)需要復(fù)用的邏輯,使用作用域插槽,將父組件的狀態(tài)共享給子組件。代碼實(shí)現(xiàn)如下:
<script>
const { createApp } = Vue
const MouseMove = {
data() {
return {
x: 0,
y: 0
}
},
methods: {
handleMouseMove(e) {
this.x = e.pageX
this.y = e.pageY
}
},
mounted() {
window.addEventListener('mousemove', this.handleMouseMove)
},
unmounted() {
window.removeEventListener('mousemove', this.handleMouseMove)
},
// 等價(jià)于 template: `<slot :x="x" :y="y"></slot>`,
render() {
return this.$slots.default && this.$slots.default({
x: this.x,
y: this.y
})
}
}
const App = {
template: `<MouseMove v-slot="{x, y}">{{x}} {{y}}</MouseMove>`,
components: { MouseMove }
}
createApp(App).mount('#app')
</script>我們還是來分析下這種方式的優(yōu)缺點(diǎn),還是通過假設(shè)我們需要重用多個(gè)邏輯,把mixins: [ MouseMoveMixin, anthorMixin, fooMixin ]改寫為使用作用域插槽:
const MouseMove = {
}
const Foo = {
}
const Anthor = {
}
const App = {
template: `
<MouseMove v-slot="{ x, y }">
<Foo v-slot="{ foo }">
<Anthor v-slot="{ anthor }">
{{x}} {{y}} {{foo}} {{anthor}}
</Anthor>
</Foo>
</MouseMove>`,
components: { MouseMove, Foo, Anthor }
}
createApp(App).mount('#app')看上去是解決了上面兩個(gè)問題了,我們能夠很明顯的看到每個(gè)屬性是從哪個(gè)組件注入的,來源清晰了,即使有命名的問題,我們在解構(gòu)的時(shí)候是可以重命名避免的,比如Foo注入的也叫x,那我們可以這么寫<Foo v-slot="{ x: foo }">。
那是不是這樣就完美了呢?并沒有,細(xì)心的同學(xué)可能發(fā)現(xiàn)了,我們?yōu)榱藦?fù)用邏輯導(dǎo)致了更多的組件實(shí)例創(chuàng)建,是不是有點(diǎn)魚和熊掌不可兼得的感覺,我們接下來看Vue.js的終極大招——Composition API 組合式函數(shù)。
Composition API 組合式函數(shù)
先簡單介紹下Composition API:
組合式 API (Composition API) 是一系列 API 的集合,使我們可以使用函數(shù)而不是聲明選項(xiàng)的方式書寫 Vue 組件。它包含了這些API:
- 響應(yīng)式API —— ref、reactive computed、watch......
- 生命周期鉤子 —— onMounted、onUnmounted......
- 依賴注入 —— provide、inject......
接著我們用Composition API來實(shí)現(xiàn)一下:
<script>
const { createApp, ref, onMounted, onUnmounted } = Vue
function useMouseMove() {
const x = ref(0)
const y = ref(0)
const handleMouseMove = e => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', handleMouseMove)
})
onUnmounted(() => {
window.removeEventListener('mousemove', handleMouseMove)
})
return { x, y }
}
const App = {
setup() {
const { x, y } = useMouseMove()
return { x, y }
},
template: `{{x}} {{y}}`,
}
createApp(App).mount('#app')
</script>看完這個(gè)實(shí)現(xiàn),首先它肯定是沒有以上的各種問題的,同時(shí)Composition API也是Vue3的一個(gè)重大更新,能夠讓我們更輕松的組織我們的邏輯代碼,更輕松的達(dá)到邏輯復(fù)用,可謂是完美方案!
可能你還有點(diǎn)小問題,比如setup為啥要先解構(gòu),再返回 { x, y }。
能直接返回useMouseMove()嗎
const App = {
setup() {
return useMouseMove()
},
template: `{{x}} {{y}}`,
}答:如果你沒有其他變量需要暴露出去,你當(dāng)然可以直接返回useMouseMove()。但是直接返回useMouseMove(),那又回到了之前的問題,又不能清晰地看出哪個(gè)變量是哪個(gè)組合式函數(shù)注入的。
我能不能在return的對象里解構(gòu)
const App = {
setup() {
return {
...useMouseMove()
}
},
template: `{{x}} {{y}}`,
}答:可以,但不推薦,這么寫還是又回到了之前的問題。
最佳實(shí)踐
const App = {
setup() {
const { x, y } = useMouseMove()
return { x, y }
},
template: `{{x}} {{y}}`,
}總結(jié)
本文用Vue.js四種邏輯復(fù)用的方案實(shí)現(xiàn)了 鼠標(biāo)位置 的例子,并且分析了每種方案的優(yōu)缺點(diǎn)。
- mixin —— 存在 命名沖突、變量來源不清
- 高階組件 —— 存在 命名沖突、變量來源不清
- 作用域插槽(scoped slots)—— 為了邏輯復(fù)用導(dǎo)致更多組件實(shí)例創(chuàng)建,得不償失
- Composition API 組合式函數(shù) —— 完美方案
相信讀完本文,你一定學(xué)到了在Vue.js搭建的應(yīng)用中實(shí)現(xiàn)代碼邏輯復(fù)用的最佳姿勢!
以上就是4種方案帶你探索Vue代碼復(fù)用的前世今生的詳細(xì)內(nèi)容,更多關(guān)于Vue代碼復(fù)用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
VUE2實(shí)現(xiàn)事件驅(qū)動(dòng)彈窗示例
本篇文章主要介紹了VUE2實(shí)現(xiàn)事件驅(qū)動(dòng)彈窗示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10
Vue+FormData+axios實(shí)現(xiàn)圖片上傳功能的項(xiàng)目實(shí)戰(zhàn)
本文主要介紹了Vue+FormData+axios實(shí)現(xiàn)圖片上傳功能的項(xiàng)目實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
Vue.js集成Word實(shí)現(xiàn)在線編輯功能
在現(xiàn)代Web應(yīng)用中,集成文檔編輯功能變得越來越常見,特別是在協(xié)作環(huán)境中,能夠直接在Web應(yīng)用內(nèi)編輯Word文檔可以極大地提高工作效率,本文將詳細(xì)介紹如何在Vue.js項(xiàng)目中集成Word在線編輯功能,需要的朋友可以參考下2024-08-08
詳解vue移動(dòng)端項(xiàng)目的適配(以mint-ui為例)
這篇文章主要介紹了詳解vue移動(dòng)端項(xiàng)目的適配(以mint-ui為例),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08
karma+webpack搭建vue單元測試環(huán)境的方法示例
本篇文章主要介紹了karma+webpack搭建vue單元測試環(huán)境的方法示例,這次搭建的測試環(huán)境和開發(fā)環(huán)境隔離,所以理論上適用所有使用vue的開發(fā)環(huán)境。感興趣的小伙伴們可以參考一下2018-05-05
el-date-picker日期范圍限制的實(shí)現(xiàn)
本文主要介紹了el-date-picker日期范圍限制的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05
Vue?打包優(yōu)化之externals抽離公共的第三方庫詳解
這篇文章主要為大家介紹了Vue?打包優(yōu)化之externals抽離公共的第三方庫詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2023-06-06
Vue-cli3項(xiàng)目引入Typescript的實(shí)現(xiàn)方法
這篇文章主要介紹了Vue-cli3項(xiàng)目引入Typescript的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10

