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