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