前端組件級(jí)別的抽象方向

基于2大主流前端框架下,前端的主要的工作其實(shí)就是在編寫(xiě)各種組件。似乎所有人都在說(shuō)前端開(kāi)發(fā)的天花板很低,除了一些特別的方向,很少有人在前端功能的開(kāi)發(fā)上遇到過(guò)什么難題。這也導(dǎo)致沒(méi)有人關(guān)注和討論前端組件的抽象,大家都忙于做功能的開(kāi)發(fā)。甚至有些前端開(kāi)發(fā)在完全不做抽象的情況下僅用少數(shù)幾個(gè)組件就完成了對(duì)整個(gè)SPA的開(kāi)發(fā)。

為什么需要組件級(jí)別的模塊抽象?
即使在掘金這樣偏前端的專(zhuān)業(yè)社區(qū),也有很多人認(rèn)為不需要做模塊抽象。以下是我的上一篇文章《如何把前端項(xiàng)目寫(xiě)成一座屎山?[1]》下的一個(gè)熱門(mén)評(píng)論:

具體就不做反駁了,因?yàn)槲腋静恢浪谡f(shuō)什么!

模塊化抽象的本質(zhì)仍舊也逃離不了“高內(nèi)聚、低耦合”這2個(gè)永恒的主題。更具體的,主要是為了性能和可維護(hù)性。但性能問(wèn)題往往被直接忽略,因?yàn)楝F(xiàn)代瀏覽器的性能和框架的優(yōu)化極大抹平了這部分差異。所以?huà)侀_(kāi)一些情況下的性能優(yōu)化,我們抽象的意義在于提高了代碼的可維護(hù)性,帶來(lái)了復(fù)用、邏輯內(nèi)聚、穩(wěn)定的開(kāi)發(fā)效率、較小的心智負(fù)擔(dān)和bugfree,所以這絕對(duì)是一件具有長(zhǎng)期收益的事情。

抽象方向
代碼行數(shù)
相信大部分前端開(kāi)發(fā)都體會(huì)過(guò)接手別人代碼,打開(kāi)一個(gè)組件模塊,代碼1000+行那種撲面而來(lái)的壓迫感。雖然我也看到過(guò)代碼行數(shù)控制在小幾百行,抽象依舊稀爛的代碼。但暫時(shí)沒(méi)見(jiàn)過(guò)組件超長(zhǎng)但抽象仍舊非常好的代碼。代碼行數(shù)甚至稱(chēng)不上作為衡量抽象好壞的標(biāo)準(zhǔn),但絕對(duì)是最接近本質(zhì)的一個(gè)表象。就我個(gè)人代碼風(fēng)格而言,極少有封裝超過(guò)150行的組件模塊(代碼風(fēng)格可參考本人開(kāi)源項(xiàng)目KerryCodes/leggo: 一款拖拽式前端表單生成低代碼工具~ \(github.com\)[2])。

視圖頁(yè)面結(jié)構(gòu)
這是最樸素的一個(gè)組件抽象方向,社區(qū)里討論的也最多,按下不表。

單一功能
很多開(kāi)發(fā)總是困惑什么時(shí)候應(yīng)該開(kāi)始抽象組件,是因?yàn)檫@個(gè)組件太大了代碼行數(shù)太多了,所以需要去做拆分嗎?是因?yàn)橐晥D結(jié)構(gòu)上相距甚遠(yuǎn)所以拆分嗎?不是的,你應(yīng)該要考慮的是功能。根據(jù)“單一職能”原則,我們可以基于功能去安排將哪些邏輯抽象成一個(gè)獨(dú)立的組件模塊。多思考功能級(jí)別的拆分,然后把每一個(gè)最小原子化功能實(shí)現(xiàn)的代碼抽象成一個(gè)獨(dú)立的組件。

復(fù)用公共部分
我們難免遇到一些功能相似的組件,導(dǎo)致在多個(gè)組件中重寫(xiě)了一部分相同或相似的邏輯。不要這樣做,盡量把交叉部分的功能做抽象,達(dá)到復(fù)用的效果。這里的抽象可以是公共組件也可以是一個(gè)公共函數(shù)。如果復(fù)用和維護(hù)性無(wú)法權(quán)衡怎么辦?維護(hù)性?xún)?yōu)先!不要為了復(fù)用一點(diǎn)邏輯搞一堆入?yún)⒒蛱幚泶罅康倪吔鐥l件,導(dǎo)致代碼冗雜難懂,這是不可取的。






自動(dòng)初始化
這是一個(gè)非常常見(jiàn)的功能場(chǎng)景。假設(shè)你有一個(gè)新建彈窗,里面有一些表單在彈窗開(kāi)閉的過(guò)程中需要恢復(fù)初始化值。最常規(guī)的開(kāi)發(fā)就是通過(guò)手動(dòng)去恢復(fù)初始值。但隨著功能的迭代表單值的增刪變化,我們不得不同時(shí)也不斷去維護(hù)一大塊邏輯用于初始化,這大大增加了開(kāi)發(fā)的心智負(fù)擔(dān),導(dǎo)致維護(hù)性性變差。所以,我們的抽象在于使得狀態(tài)初始化過(guò)程不需要手動(dòng)去干預(yù),而交給組件的加載和卸載過(guò)程自動(dòng)去完成。

自定義hooks
相比于Class組件,函數(shù)組件提供了hooks,于是我們有了一個(gè)非常強(qiáng)大的帶狀態(tài)的抽象復(fù)用手段。明顯的,我們可以將一些公共的業(yè)務(wù)邏輯抽象成hooks作為復(fù)用手段(當(dāng)然也更應(yīng)該首先考慮抽象成一個(gè)組件)。另外對(duì)于一些非公共的業(yè)務(wù)邏輯,只被某個(gè)特定組件使用,但是卻比較復(fù)雜,比較偏過(guò)程,個(gè)人也會(huì)封裝成hooks使用。這樣做的好處就在于組件內(nèi)部會(huì)比較干凈,開(kāi)發(fā)維護(hù)的心智負(fù)擔(dān)下降。

但我們也并非可以濫用自定義hooks,因?yàn)閔ooks也有一些明顯的缺點(diǎn)。比如副作用,隱式的狀態(tài),邏輯黑盒化等等,這些都增加了心智負(fù)擔(dān)。(歡迎各位大佬討論這部分內(nèi)容?。?br>
狀態(tài)和重渲染
“狀態(tài)”可以說(shuō)是前端組件開(kāi)發(fā)中最重要的一個(gè)概念,所有的重渲染都是來(lái)自于狀態(tài)的改變。很多bug也是來(lái)自于狀態(tài)與視圖不一致的問(wèn)題。狀態(tài)維護(hù)的好壞標(biāo)準(zhǔn)在于狀態(tài)的改變是否引起的是最小原子化的視圖重渲染。如果一個(gè)狀態(tài)的改變總是引起與該狀態(tài)毫無(wú)關(guān)系的其它組件重渲染,那么基本可以確定這一塊代碼的抽象做的很差。這里可以特別提一下“純組件”的優(yōu)化,出現(xiàn)這種優(yōu)化技巧的本質(zhì)就在于狀態(tài)改變所導(dǎo)致的不必要高成本重渲染。

有一種非常典型的抽象方式就是props.children,也可以歸為這個(gè)方向。這個(gè)寫(xiě)法相信很多前端都了解,在面試過(guò)程中也是一個(gè)高頻八股問(wèn)題。但這種方式不應(yīng)該被濫用,因?yàn)檫@會(huì)導(dǎo)致代碼視圖結(jié)構(gòu)的清晰度下降。我總結(jié)了適用這種抽象的幾個(gè)前提:頁(yè)面視圖結(jié)構(gòu)導(dǎo)致父子組件無(wú)法分離;父子組件關(guān)聯(lián)度很低;父組件的狀態(tài)活躍,而子組件不活躍。本質(zhì)上在于父組件的狀態(tài)變化不引發(fā)子組件的不必要重渲染。

非受控組件
這個(gè)概念來(lái)自React官方文檔介紹表單組件的部分,我們可以借用一下。如果你的組件總是需要接受來(lái)自父組件的傳參,可能就是抽象不好的體現(xiàn)。越多的傳參代表更多的耦合度。

軟件設(shè)計(jì)有一個(gè)非常重要的原則叫“迪米特原則”,這個(gè)原則啟發(fā)我們一個(gè)模塊應(yīng)當(dāng)盡可能少的與其他模塊發(fā)生相互作用。盡量使你的組件保持干凈獨(dú)立,狀態(tài)在內(nèi)部完成消化而不是受父組件控制。除了耦合度降低帶來(lái)的bugfree,一個(gè)明顯的收益是干凈的非受控組件在復(fù)用時(shí)幾乎是沒(méi)有心智負(fù)擔(dān)的。所以需要非常謹(jǐn)慎的使用狀態(tài)提升這種手段,如非必要可以盡量避免。

申明:以上觀點(diǎn)來(lái)自個(gè)人經(jīng)驗(yàn)總結(jié)和思考,歡迎理性討論和補(bǔ)充。另外所有方式都需要先考慮具體場(chǎng)景,不要無(wú)腦使用!

關(guān)于本文

作者:阿佛加德奔

https://juejin.cn/post/7156575298501214221

歡迎關(guān)注微信公眾號(hào) :前端Q