用OOP的思想設計前端組件

晚上吃飯的時候我和同事聊了下對於前端組件設計的理解,之後在地鐵上又在腦中對比了下react/vue/ng2對於組件封裝的不同方式,感覺有些啟發。

相信大家都有體會:由於和業務緊密掛鉤,前端代碼很容易走向面向過程而非面向對象的開發模式。 對於常見的imgSlide、dialog等高頻復用組件,大部分也都是按照寫html、css、對dom節點添加事件這套流程來開發的。 碰到公司需求迭代、UI優化等場景,相關同學不得不進行css樣式覆蓋、對JS做邏輯兼容的方式來處理。 這樣滾雪球式的代碼開發方式和前端組件的維護成本是呈線性關係的,當然相關開發人員也會越來越糾結,最終萌生重構的想法。

對於使用React、Vue、ng2這類高度封裝和組件化的框架,由於框架的約束,組件開發都會按照同一套規則進行。框架中的state、props、input output等概念已經基本hold住相關場景了。那麼,對於服務端渲染模式為主的PC和mobile主站,在不使用任何框架的情況下,如何編寫高質量、可維護、擴展性強的組件呢?

按照MVC的功能模塊來劃分,一個組件可以劃分為view + model + controller。我們可以把一個大的組件看成多個子組件的集合,而每個子組件都具備MVC這三個要素。

對於子組件而言,它的model可以分為兩部分。一部分可以交由自身維護(比如顏色、文字等),另一部分涉及到組件通信的數據則通過類似setState或者eventEmitter的形式交由父組件統一分發給子組件進行UI同步。

就像react的render函數或者vue的component一樣,要想封裝出來一套具有如上功能的組件,我們需要給組件定義設計規範。

按照OOP的編程思想,可以進行如下幾步定義。

子組件(dumb components):

1. 定義所有的子組件都是個class,都需要通過構造函數進行實例化來初始化UI及內部狀態。

2. 定義所有的子組件實例必須有digestUI方法,以便提供給父組件調用,從而同步自身UI。

3. 定義所有的子組件實例都共享父組件的setState方法,以通知父組件根據新的model來同步組件UI。

父組件(controller):

1. 父組件在實例化的同時,也統一實例化所有的子組件。

2. 給所有的子組件實例提供setState方法,以便暴露子組件給父組件傳遞消息的方法入口。

3. 在子組件執行setState通知父組件的時候,遍歷調用子組件的digestUI方法,根據model同步子組件UI。

按照如上功能進行父子組件劃分和功能封裝,可以讓所有的子組件彼此絕緣,而所有的UI同步則依賴父組件進行統一分發。同時,在這種開發模式下,開發者還可以在實例化父組件的時候,對父組件做初始數據model的extend覆蓋、對setState添加subscribe回調(類似redux)來擴展組件的功能,而不需要修改原來的邏輯。

無碼無真相,然而夜已經深了,先不貼基本的代碼實現。

最後,關於使用JS的prototype來實現一個OOP編程模型,開源社區上有很多的實現。同時,ES6/7也引入了class關鍵詞方便JSer們按照OOP的方式來編程。然而,要將OOP真的用在項目中並且使用的恰到好處,卻是需要一些設計和抽象的實踐的。 使用了這麼多框架,筆者依然對facebook出品的react情有獨鍾。對於OOP,react實在是實踐的太好了。無論是繼承(組件都繼承React.Component的方法)、方法封裝、還是抽象,都能夠在每個componenet找到對應的體現。最後需要說明的是,本文以上對組件設計的所有討論,基本都能夠在react、redux裡面找到影子,相信實踐react和redux會加深大家對於OOP及設計模式的一些理解。

推薦閱讀:

「每日一題」MVC 是什麼?(續1)
Python編程(b三):Python之MVC
輸入URL後到底發生了什麼?我用小白能明白的方式來說說!
AngularJS 沒有元素選擇器算不算一個缺點?

TAG:React | 前端开发 | MVC |