vue中拆分組件的實(shí)戰(zhàn)案例
組件化是一種思維的表現(xiàn),這種技能映射到人的本質(zhì)是,一個(gè)人是否有能力把一個(gè)復(fù)雜的問題拆解、簡(jiǎn)單化的能力。
一、組件化誕生的歷史
我們?cè)谟懻撊绾尾鸱纸M件之前,是有必要簡(jiǎn)單的了解一下組件化誕生的一個(gè)歷史。
前端娛樂圈有一個(gè)獨(dú)有的生態(tài):框架。每年出現(xiàn)的框架層出不窮,根本學(xué)不完。但是總的來說還是可以分成兩個(gè)階段。
第一階段: JQ和PrototypeJS。 該階段解決了瀏覽器的兼容性問題以及API的遍歷程度
第二階段: Vue、React、Angular。解決了組件化、解耦、復(fù)用等問題
在大陸,主要討論的是Vue和React。 有些人說Vue是framework,而React是library,前者有更多的約束和更加齊全的工具鏈。而后者更加的自由。但是真的要投入生產(chǎn)的話,依舊需求認(rèn)為的給React添加很多的約束,而且Vue也是支持jsx的,所以我一直不太贊同React更加自由這樣的說話。
在我看來,它們?cè)趯?shí)際生產(chǎn)開發(fā)過程中,在那一堆工具鏈中,只是API的不同而已。
它們都為前端提供了很好的組件化。而且近一年來兩者都不約而同朝著函數(shù)式跟進(jìn)。它們帶來的各種hook,給我們帶來了不一致的組件化的寫法。
二、為什么業(yè)務(wù)組件越開發(fā)越難維護(hù)
人的問題
當(dāng)然是人的問題. 或許產(chǎn)品的問題,或許整個(gè)工作流程的問題,或許上面的問題. 這些我們暫且不提,我作為開發(fā), 首先是要管好自己的代碼組織.
再次我們先排除其他外界的因素,比如產(chǎn)品經(jīng)常改需求. 僅從編碼階段來說.
以我們團(tuán)隊(duì)為例,我們團(tuán)隊(duì)內(nèi)部員工2個(gè),8個(gè)外包,外包兄弟們的招聘標(biāo)準(zhǔn)是遠(yuǎn)低于內(nèi)部的。團(tuán)隊(duì)人員每個(gè)人的編碼能力差距還是很大的。項(xiàng)目都是長(zhǎng)期維護(hù)的,一個(gè)業(yè)務(wù)模塊就會(huì)有很多人維護(hù),在上面不斷的填尿加屎。
在這里并不是說外包人員的編碼能力差,我們組就有一個(gè)外包的兄弟編碼能力、解決問題的能力相當(dāng)厲害的,比很多內(nèi)部的都好很多。這里只是從平局值上面來說。
團(tuán)隊(duì)成員的水平參差不齊, 顧及到團(tuán)隊(duì)協(xié)作, 我們?cè)诓鸱纸M件的時(shí)候需要更加的簡(jiǎn)單和清晰.
技術(shù)問題
業(yè)務(wù)邏輯和交互邏輯的糾纏不清
2.1 項(xiàng)目現(xiàn)狀
以該圖為例, A B C 分別是父子孫組件. 當(dāng)我們要控制其中一個(gè)組件的狀態(tài)的是, 可以通過很多方式來進(jìn)行控制. 這些方式的來源有可能是全局變量、vuex、時(shí)間總線、來自自己父組件或子組件的改變等等.
可以看出, 改變它組件內(nèi)部狀態(tài)的來源非常的多, 維護(hù)或者修改的時(shí)候,需要翻閱的文件目錄和范圍就很廣. 自然就很難維護(hù).
舉一個(gè)mixins的例子:
假設(shè)它混入了這么多功能。
export default { mixins: [ a, b, c, d, e, f, g], mounted() { console.log(this.whoAreYou) } }
這個(gè)this.whoAreYou
你能夠知道來源于哪一個(gè)么? 而如果改成hook
的寫法來引入某個(gè)JS中的變量:
const { IamI } = myHome() const { IamI as me } = myHome()
這就很簡(jiǎn)潔干凈。在你維護(hù)代碼的時(shí)候,可以很好的進(jìn)行溯源。 而上面的一切,導(dǎo)致難以維護(hù)的原因總結(jié)來說有兩個(gè):
- 混用業(yè)務(wù)變量和UI變量
- 不區(qū)分受控組件和非受控組件
下面我會(huì)實(shí)際例子分別介紹這個(gè)兩個(gè)概念。而基于hooks
的復(fù)用才是我們現(xiàn)在解決組件化復(fù)用的更好的選擇。
2.2 理想目標(biāo)
基于hook
的理想模型
依舊是A B C 三個(gè)組件.但是A B C三個(gè)組件外邊飄的那些箭頭不存在了. 所有能夠控制它們的內(nèi)部狀態(tài)的方式都集中 在了controllers
上面.
其中controllers
部分的組織形式和vue的composition api
宣傳圖表現(xiàn)一致。
將相似的功能以及用到的變量都封裝在一個(gè)函數(shù)當(dāng)中。這一切也更加好的迎合了
實(shí)際代碼如下:
<template> <div> <B setC={setC} /> <div> </template> <script setup> import B from 'B.js' import cController from 'cController.js' const { setC } = cController(props) </script> // cController.js export default c(props) { const c = ref('') const setC() { c.value = 'I an cController' } return { c, setC, } }
cController.js
就是controllers
中的一個(gè)void
. 引入到A組件當(dāng)中,然后將里面的方法通過props傳給B組件.
<template> <div> <C setC={setC} /> <div> </template> <script setup> import C from 'C.js' props: { setC: { type: Number, } } </script>
也就是說,控制C組件內(nèi)部狀態(tài)的是通過引入到A組件中的controller
來進(jìn)行通過,中間的B組件不做任何的處理,僅僅作為一個(gè)中轉(zhuǎn)站. 操作起來和理論都很簡(jiǎn)單。但是想要更好的拆分的話,還需要了解三個(gè)概念:
- 業(yè)務(wù)變臉和UI變量
- 受控組件和非受控組件
- 控制反轉(zhuǎn)ioc
下面我通過一個(gè)實(shí)際的業(yè)務(wù)場(chǎng)景來描述。
三、舉一個(gè)實(shí)際的例子
3.1 需求背景
簡(jiǎn)單的截兩張圖. 需求大致如下:
- 功能就是典型的筆記軟件的功能,右邊可以放各種類型的文件,點(diǎn)擊就可以在右邊渲染出對(duì)應(yīng)的內(nèi)容.
- 目錄樹有兩個(gè)彩蛋,會(huì)根據(jù)當(dāng)前文件類型出現(xiàn)不同的操作
- 目錄樹下面有一個(gè)固定的收藏夾,目錄樹可以在這其中滾動(dòng)
3.2 開發(fā)之前: 前端設(shè)計(jì)文檔
數(shù)據(jù)流向圖
功能還是很清楚的,但是功能其實(shí)很多. 我認(rèn)為我們團(tuán)隊(duì)在開發(fā)之前是必須要有的. 作為一個(gè)前端, 可以沒有流程圖,但是一定要下面這樣的圖. 我在別的地方?jīng)]有見過這樣的圖,所以自己給這樣的圖做了一個(gè)定義,叫數(shù)據(jù)流向圖.
關(guān)于完整的工作流程,之后再寫一篇文章進(jìn)行描述
它是有兩部分構(gòu)成:
- 組件的模塊
- 組件之間的控制關(guān)系
第一點(diǎn), 還是比較清楚,就是這個(gè)需求可以拆成哪幾個(gè)模塊.
第二點(diǎn), tree組件和content組件是同級(jí)組件, tree可以控制content組件內(nèi)的狀態(tài), content組件也可以改變tree內(nèi)的狀態(tài). 再深入一點(diǎn)說,就是tree點(diǎn)擊不同的文件類型, content組件部分就會(huì)渲染不同的模塊; 而當(dāng)在content組件內(nèi)對(duì)當(dāng)前閱讀的文件進(jìn)行刪除操作的時(shí)候,tree作為目錄樹自然是要刷新最新的目錄信息的.
目錄結(jié)構(gòu)
通過上面的結(jié)構(gòu)圖,可以得到下面這樣目錄結(jié)構(gòu).
邏輯控制
數(shù)據(jù)流向圖中的各個(gè)組件都放在根目錄下index.vue
中掛載. 如下入
控制目錄樹的相關(guān)邏輯都放在listTreeController
控制器里邊, 和右邊內(nèi)容content相關(guān)邏輯都放入到renderContentController
的方法當(dāng)中.
隨后將controller中公共方法都傳進(jìn)到組件當(dāng)中. doc-aside
是包括search
和tree
已經(jīng)other
三個(gè)模塊的中轉(zhuǎn)組件. 不在這個(gè)組件中做任何的邏輯處理. 如下圖:
舉一個(gè)例子, 控制按鈕的權(quán)限. [背景]
- 所有功能點(diǎn)都受控掛載在vuex的store上面的一個(gè)變量, 沒有權(quán)限的話,就直接通過
v-if
來隱藏對(duì)應(yīng)的入口
[之前實(shí)現(xiàn)]
- 直接找到對(duì)應(yīng)的按鈕在
v-if
上,通過root.docAuth('createDoc')
來判斷
[修改之后]
- 創(chuàng)建來一個(gè)
authoControllers.js
在index.vue
引入, 需要用的地方是應(yīng)用的是
[具體實(shí)現(xiàn)]
export default function authController({ root }) { const menuAuth = { [MENTY_TYPE.rename]: root.docAuth('rename'), [MENTY_TYPE.delete]: root.docAuth('delete') // .... } }
雖然在Index.vue中引入,不管是通過props,還是通過依賴注入來給子組件來使用,都不重要.重要的是,它統(tǒng)一管理, 并在index.vue中引入是唯一一個(gè)入口. 當(dāng)我們維護(hù)的時(shí)候, 只需要通過子組件一路找到對(duì)應(yīng)的controller
就可以找到對(duì)應(yīng)的邏輯了.
拆分的原則
- 對(duì)于組件的拆分一開始不需要太細(xì)
- 拆分好受控組件和非受控組件
3.3 受控組件和非受控組件
我們使用的任何UI框架都是受控組件, 受控組件的概念就是它里面的狀態(tài)都是受調(diào)用它的組件來控制的. 非受控組件反之.
3.4 開發(fā)進(jìn)行: 邏輯變量和UI變量
UI變量其實(shí)很好理解. 像element-ui的組件中所需要的屬性就是UI變量. 但是對(duì)于我們實(shí)際業(yè)務(wù)當(dāng)中, 會(huì)對(duì)這些進(jìn)行一定擴(kuò)展.
舉一個(gè)例子, 在上面的目錄中dialog組件
的顯示或隱藏,是通過model-value / v-model
來進(jìn)行控制的, true
就顯示, false
就隱藏起來.
隱藏和顯示的漸入漸出效果是elementUI框架內(nèi)置的.
平時(shí)工作中很多人是這樣傳的:
<el-dialog :v-model="data.id === XXXX"> // code </el-dialog> props = { data: { type: Object } }
通過通過接口拿到的,或者自己組件的數(shù)據(jù)傳進(jìn)來之后,再進(jìn)行對(duì)v-model
的控制. data.id
這樣的變量就是業(yè)務(wù)變量, 通過業(yè)務(wù)變量來直接控制UI的組件的顯示和隱藏,就是業(yè)務(wù)變量和UI變量的混用. 或者說**業(yè)務(wù)邏輯和交互邏輯的混用. **
混用之后的后果,就是我們進(jìn)行維護(hù)的時(shí)候, 需要查看的變量或者說字段就成倍的增加, 交互變量和業(yè)務(wù)變量交織在一起. 這部分的代碼同時(shí)承載了業(yè)務(wù)邏輯和交互邏輯.
DDD領(lǐng)域模型也是可以解決這個(gè)問題, 之后我會(huì)再開篇幅聊一聊.
所以我們就需要將業(yè)務(wù)邏輯和交互邏輯給拆開. 如下:
<template> <el-dialog :v-model="isShow"> <template slot="header"> {{ dialogTitle }} </template> <template slot="content"> // type === 創(chuàng)建表單 // type === 移動(dòng)文件夾目錄 </template> </el-dialog> </temaplte> props = { isShow: { type : Boolean, desc: '是否顯示彈窗' }, type: { type: String, desc: '彈窗的類型' } }
其中ishow
和type
就可以視為UI變量, 它們不關(guān)心外界是通過了什么判斷, 只關(guān)系傳進(jìn)來的是true還是false.
四、持續(xù)的優(yōu)化
不管一開始代碼是如何規(guī)劃的,如何組織的.最重要的還是要持續(xù)的去維護(hù). 屎山到了之后, 前面的維護(hù)者沒有一個(gè)人是無辜的. 但是也不需要過早的去維護(hù).什么時(shí)候到了維護(hù)重構(gòu)的時(shí)機(jī)呢?
- 當(dāng)碰到這里用的代碼別的地方也用到的時(shí)候
- 這個(gè)變量出現(xiàn)在好幾個(gè)地方,被好幾個(gè)地方都set的時(shí)候, 而自己搞不懂它們set的順序的時(shí)候
- 函數(shù)復(fù)雜到自己看了半天都看不明白的時(shí)候
五、可能的問題
問題一: 中轉(zhuǎn)的組件沒有掛載任何邏輯,為什么還存在?
- 為了之后可能的拆分
- 讓結(jié)構(gòu)更加的清晰
問題二: 中轉(zhuǎn)的組件要掛載這么多辦法, 或許太難看?
- 實(shí)在是太多可以使用vue的
$attr
和$listeners
- 為了維護(hù)對(duì)于數(shù)據(jù)的溯源
五、實(shí)踐是學(xué)習(xí)前端的捷徑
前端是一門手藝活,只有實(shí)踐才能夠提高技術(shù). 前端的天花板確實(shí)相比其他方向的低,但是也不是我這樣的普通人說能夠觸碰就能觸碰到的. 就算很多高端大佬嗤之以鼻的業(yè)務(wù)代碼, 寫的時(shí)候如果不多思考如何寫的簡(jiǎn)潔,怎么寫優(yōu)雅,寫十年和寫三年也是沒有差別的.
業(yè)務(wù)才能創(chuàng)造價(jià)值, 有了價(jià)值才能有我們前端工程師生存的空間. 所以為了提升自己的價(jià)值, 提升自己的工資. 平時(shí)寫業(yè)務(wù)代碼的時(shí)候,想想這樣寫會(huì)有什么問題, 如何寫才能夠更加好. 在這個(gè)基礎(chǔ)上, 才能看明白那些框架存在的意義. 業(yè)務(wù)是在輪子之上的,如果對(duì)業(yè)務(wù)的代碼都不理解, 又怎么能夠真正的寫好輪子呢?
所以我們?cè)诒U蠘I(yè)務(wù)按時(shí)完成的情況下,應(yīng)該多嘗試,多實(shí)踐.
總結(jié)
到此這篇關(guān)于vue中拆分組件的文章就介紹到這了,更多相關(guān)vue拆分組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue-cli項(xiàng)目修改文件熱重載失效的解決方法
今天小編就為大家分享一篇vue-cli項(xiàng)目修改文件熱重載失效的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-09-09利用Vue3和element-plus實(shí)現(xiàn)圖片上傳組件
element-plus提供了uploader組件,但是不好定制化。所以本文將利用Vue3和element-plus實(shí)現(xiàn)一個(gè)圖片上傳的組件,感興趣的可以了解一下2022-03-03Vue2(三)實(shí)現(xiàn)子菜單展開收縮,帶動(dòng)畫效果實(shí)現(xiàn)方法
這篇文章主要介紹了vue實(shí)現(xiàn)收縮展開效果的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04vue2和elementUI?實(shí)現(xiàn)落日余暉登錄頁和滑塊校驗(yàn)功能
這篇文章主要介紹了vue2和elementUI打造落日余暉登錄頁和滑塊校驗(yàn),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06Vue開發(fā)實(shí)現(xiàn)吸頂效果的示例代碼
這篇文章主要介紹了Vue開發(fā)實(shí)現(xiàn)吸頂效果的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08前端elementUI?select選擇器實(shí)現(xiàn)遠(yuǎn)程搜索
這篇文章主要為大家介紹了前端使用elementUI?select選擇器實(shí)現(xiàn)遠(yuǎn)程搜索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05淺談VUE單頁應(yīng)用首屏加載速度優(yōu)化方案
這篇文章主要介紹了淺談VUE單頁應(yīng)用首屏加載速度優(yōu)化方案,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08Vue默認(rèn)插槽,具名插槽,作用域插槽定義及使用方法
這篇文章主要介紹了Vue默認(rèn)插槽,具名插槽,作用域插槽定義及使用方法,插槽的作用是在子組件中某個(gè)位置插入父組件的自定義html結(jié)構(gòu)和data數(shù)據(jù),下面詳細(xì)內(nèi)容需要的小伙伴可以參考一下2022-03-03vue頁面回退或關(guān)閉,發(fā)送請(qǐng)求不中斷問題
這篇文章主要介紹了vue頁面回退或關(guān)閉,發(fā)送請(qǐng)求不中斷問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01