這是第 79 篇不摻水的原創,想獲取更多原創好文,請搜索公衆號關注咱們吧~ 本文首發於政採雲前端博客: 編寫高質量可維護的代碼:組件的抽象與粒度
做爲一名精緻的前端豬豬女孩,也有那麼點想讓本身的代碼一樣看起來精緻一點。因此在拿到新需求的 UI 設計稿時,常常會面臨以下問題:如何拆解頁面?如何劃分組件纔算是合理?好像用於組件拆分的 A 方案和 B 方案在當前業務場景下也都還算合理,那究竟要怎麼選擇?組件的抽象與粒度貌似是一個老生常談的問題了~學習了不少前輩的文章,那麼今天結合業務場景,也來說下個人心得~html
React 官方文檔上說:組件,從概念上相似於 JavaScript 函數。它接受任意的入參(即 「props」),並返回用於描述頁面展現內容的 React 元素。前端
Vue 官方文檔說:組件是可複用的 Vue 實例,且帶有一個名字。咱們能夠在一個經過 new Vue
建立的 Vue 根實例中,把這個組件做爲自定義元素來使用。vue
其實總的來講,不管什麼語言框架,組件就是一段代碼片斷,它能夠實現某些指定的功能或渲染特定的展現效果,咱們通常能夠經過 import
的方式將其引入到項目代碼中。本文接下來將以 React 爲例進行相關描述。react
組件抽象的過程就是將通用性代碼「提取」或是「抽取」出去的過程,那麼問題來了,咱們爲何要抽組件呢?數據結構
說到爲何要抽取組件,不知道各位讀者有沒有遇到過一個 js 文件中有 1k+ ~ 2k+ 行 React 代碼,甚至更多行代碼的狀況。這種狀況每每致使代碼難以維護,當有新的需求涉及相關改動時,在必定程度上增長了代碼的學習成本(特別是當你剛剛新接手了一份徹底不熟悉的項目的時候)。框架
其次,某些狀況下,有一部分代碼在不一樣場景下實際上是能夠複用的,例如新增和編輯的彈窗,可能只有彈窗的標題和某些字段有部分差別,此時不必把高度類似的代碼複製兩遍,增長代碼的冗餘。ide
所以,在咱們平常開發中,組件抽取是有必要的,其目的在於代碼的分層和複用,下降項目的複雜度。函數
單一性要求一個組件具備高內聚,低耦合的特徵,它只負責一件事情,不要耦合一些不必的邏輯,而且儘可能不要和其餘組件有過於多的雙向交互和互相依賴關係。單一性並不表明着不能夠引用其餘組件,當前組件多是外層的容器組件,裏面包含一些子組件,這樣的設計是沒問題的。組件化
在設計組件的時候,必定要考慮組件的複用性或者說是通用性。這是指,當組件封裝好後,能夠在相似的使用場景中直接調用。這要求咱們在設計組件的時候,考慮組件功能的通用性,以及考慮組件入參的合理性。佈局
此時有兩種狀況:
一種是不少不一樣的項目間,可能存在相似的使用場景,所以會提煉出一個公共的組件,爲了複用。通常咱們稱之爲基礎組件或業務組件,姑且叫它公共組件吧。
另外一種是在項目內部,僅在當前場景下做爲一個獨立的模塊能夠抽取出來做爲一個組件,暫時稱之爲項目組件。
公共組件和項目組件在設計上的側重也有所不一樣,公共組件要更多的考慮通用性,經過一個組件知足不一樣項目中類似的使用場景,好比 AntD 基礎組件庫。而項目組件更多的是處理當前業務中的特殊場景,多是頁面拆解後的不一樣模塊,也多是不一樣操做的彈窗,每每這種組件不適合直接「移植」到其餘項目中使用。
然鵝,對於一個組件來講,我的認爲也不能一味的追求通用性使其變得難以維護。例如,當遇到下述頁面的時候,要如何抽象組件呢?
不難發現,頁面中交易方式、基礎配置和合同設置這三個模塊實際上是具備必定共性的,所有呈現爲列表形式,只是在某些列上有展現差別。前輩的作法是,考慮了全部狀況,抽象成一個組件。經過 title 區分模塊名稱,因爲僅在交易方式模塊有操做列,所以經過 areaCode 區分當前頁面下的不一樣模塊等。
<TableConfiguration // 基本參數 title="基礎配置" // 標題名稱 data={baseSettingData} // 展現數據 areaCode="baseSettingConfig" // 模塊 code config={baseSettingConfig} // 一些業務邏輯參數 // 新增參數 pageId={this.pageId} // 當前頁面 Id userIdentity={userIdentity} // 用戶身份 />
在業務發展前期,這樣抽取的組件的確使用起來很方便,且通用性很強。但隨着業務的膨脹,同一項目中不一樣頁面開始出現相相似的模塊,因而新增 pageId 標識,用於區分不一樣的頁面以及對應頁面的特殊邏輯。又過了一段時間,新增 userIdentity 標識,用於控制不一樣登陸用戶對頁面的查看或操做權限。
久而久之,新增的參數愈來愈多,組件內部開始出現大量的判斷邏輯,儘管這個組件通用性很好,能應對各類頁面展現邏輯,但這也使它自己變得逐漸難以維護。還有一種比較好的解決方案是經過表單中心生成一份這樣的頁面,可參考動態化表單設計。
師父曾教導我說抽組件最好作一下業務層和視圖層的分離處理,其中視圖層主要負責頁面展現樣式和交互,業務層主要負責處理業務邏輯,好比接口調用,數據結構調整等。這樣作的好處除了職責分離,還能夠有效提升組件性能(好比視圖層能夠用 PureComponent 處理)。
另外,例如上述的新增和編輯彈窗,當新增和編輯兩個操做須要分別調用不一樣接口時,業務層和視圖層的分離處理能夠避免組件中耦合對「新增」或「編輯」的判斷,它們能夠共用一個視圖,並在各自的業務層實現不一樣的業務邏輯。
業務組件側重於數據和業務的邏輯處理,其中數據通常經過接口獲取。目前本團隊維護的業務組件庫,可使開發人員即來即用,組件內部有完善的功能和接口數據處理,將組件引入到項目後可直接實現對應功能。
UI 組件通常也能夠稱爲基礎組件,它們常常在多個地方被複用,且不耦合任何的業務功能,例如:AntD 組件庫。UI 組件側重於頁面展現效果,大部分 UI 組件具備原子性,一些複雜的 UI 組件能夠由基本的 UI 組件構成。通常狀況下組件內部的數據來源於父組件傳遞過來的 props。
有一天,我看到前輩大神這麼寫的代碼
export default class NotFound extends PureComponent { // 此處省略具體代碼 }
因而去學習了下純組件和非純組件的區別,首先讓咱們瞭解下React 中的各類組件一文中對 React 組件從新渲染機制的描述:
通常當一個組件的 props (屬性)或者 state (狀態)發生改變的時候,也就是父組件傳遞進來的 props 發生變化或者使用 this.setState
函數時,組件都會進行從新渲染。
而在接受到新的 props 或者 state 到組件更新之間,其實會執行生命週期中的一個方法 shouldComponentUpdate
,當該方法返回 true
時纔會進行重渲染,若是返回 false
則不會進行重渲染。
純組件和非純組件的區別在於,通常狀況下非純組件並未自動實現 shouldComponentUpdate 方法的功能(但能夠手動調用這個鉤子),而純組件中利用 shallowEqual
的方法對 props 和 state 作淺比較實現了該功能。實際應用中,純組件通常用於純展現型組件,相對於非純組件來講,減小了手動判斷 props 或者 state 變化的繁瑣操做。而且,純組件能夠經過減小 render
調用次數來下降性能損耗,可是使用過程當中也必定要確保此類組件的渲染僅取決於 props 與 state。
非純組件的話,其實咱們平常開發中比較經常使用。通常狀況下,在不作特殊處理時,正常 extends Component
出來的組件均可以認爲是非純組件。
export default class MyComponent extends Component {}
咱們能夠根據實際的開發場景選擇繼承自 PureComponent
仍是 Component
。值得注意的是,因爲純組件中作的是淺比較,所以帶有深層嵌套的數據是對比不出來的,請慎用~
提到組件的粒度,大多數人的第一反應可能認爲拆分的越細越好。可是,這樣必定是最優解嘛?我的認爲其實不是的。
組件拆解的過於細緻可能致使某些參數從父組件開始一層層向子組件傳遞,容易漏傳,錯傳,或者其中某層組件忘記判空的時候,可能會致使頁面報錯。雖然能夠經過 React Context 去獲取,不過好像仍是「徒手傳遞」的人更多一點。但組件若是拆解的太粗略每每也會致使複用率低、難以維護等問題。
講到這裏,讓我想到了原子設計。原子設計是 Brad Forst 於 2013 年提出的設計概念,該做者用 5 個層級來描述組件庫的設計。作下類比,映射到開發人員使用和熟知的組件中,我的認爲也適合描述組件粒度。
若是說,原子是物質的基本組成部分,那麼原子組件就能夠做爲構成咱們全部頁面的最基本組成部分。原子組件,能夠爲上文中提到的基礎 UI 組件,例如一個 Input 或一個 Button。它們每每具備不可再拆分的特性,是其餘組件的基礎。
分子組件通常由幾個簡單的原子組件組成,好比由一個 Label 和一個 Input 組成的姓名輸入組件。這種粒度的組件初步具備必定形態和自身屬性,與原子組件相比,有必定的可操做性。
生物組件是由原子組件及分子組件組成的相對複雜的構成物,它是一種做爲一個單元發揮做用的集合體。好比由姓名輸入組件和一組按鈕組成的搜索組件。在這個組件中,姓名輸入組件被放置在一種使用環境中,實現了簡單的功能。
有些生物組件是由不一樣的分子組件構成,但也有可能由相同的分子組件構成,好比網站首頁的商品展現組件,該組件由六宮格組成,每一個格子使用同一個分子組件進行渲染和展現。
模板組件是由原子、分子、生物組件按照必定佈局結構組成的區塊。它們專一於頁面的基礎內容結構,而不是頁面的最終內容。模板組件是更復雜一點的生物組件,更多的賦能於功能和展現。
最終,經過不一樣模板組件的拼裝,能夠生成一個完整的頁面。
在實際應用中,組件設計時的粒度每每也須要依據具體的場景具體分析,但原則能夠參考高內聚,低耦合的思路,使本身的組件易於維護,同時使本身的整個項目代碼看起來乾淨利落。
其實,本人真心認爲組件的抽象與抽象粒度這件事,沒有一個一成不變的統一標準,也沒有對與錯。在基本原則不變的狀況下,更多的應該去關注如何適配不一樣的業務場景和需求要求,求的是「適合」。有時,一樣的場景,組件粒度的標準也會隨業務場景變化而變化,甚至可能隨場景而持續重構。不過爲了代碼更好的維護和分層,以及避免代碼邏輯的過分疊加和膨脹,團隊中能夠制定一些組件抽象的規範稍稍加以約束。
政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 40 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。
若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com