这可能是大型复杂项目下数据流的最佳实践
簡介: 實際項目中沉淀的數據流最佳實踐。
?
?
數據流是前端一直以來都存在的一個問題,我們項目沉淀了一套最佳實踐,如有問題,歡迎探討
在舊的 Done 項目中,代碼復雜度高,已經到了“牽一發而動全身”,技術債極高的情況。由于舊代碼“錯綜復雜”,導致實現一個簡單的功能,都需要比正常時間多2~3倍的工作估時。就像下面這張圖的情況一樣。
?
我們仔細分析下現有的業務,會得出下面的業務特性:
基于上面的業務特性,我們再分析目前項目中的問題:
- 模板代碼過多,影響開發效率和可維護性
- 數據流螺旋呈網狀調用(強耦合),代碼復雜度急劇上升,牽一發而動全身
- 數據全局化
- 原始數據與展示數據轉換
- UI 與 數據邏輯耦合,復用低
根據上面的特點和問題,我們有以下的訴求:
基于上面的問題和分析,下面將一步步推導新的架構圖 & 技術選型。
問題分析
一、 UI 與 數據邏輯耦合復用度低 & 原始數據與展示數據轉換
原先的整體架構如下:Store 與 視圖層混合在一起, 一起處理用戶行為和業務邏輯,耦合度高,復用率低。
?
架構演進:
?
改進點:獨立的 Store 層, 封裝應用程序與業務邏輯相關的數據以及對數據的處理方法, 在此處獨立寫邏輯,支持多處組件復用一個邏輯,與視圖層獨立, 一個邏輯可以復用于多個組件。
眾所周知,分層是為了解耦。假設 Store 層發生了改變,那么在視圖層不需要變動的,只需要修改 Store 層即可,這樣改動的地方就少了,也提升了開發效率&可維護性。
但是,我們還遇到一些場景,當后端接口發生字段變動,要改動的地方實在太多太多。同一個接口,在不同地方展示,由于多人維護,他會經過多次數據轉換,最終映射到前端界面。因此,我們再多一個API防腐層,專門處理前端界面和后臺界面的數據轉換,這樣,一旦數據結構發生變化,我們也只需要在 API 層修改即可,無須關注到多個界面組件中。
?
改進點:獨立 API 層, 銜接后端服務與前端服務, 若后臺接口發生變化,可在此處進行統一修改,無需多處代碼進行修改,方便在該層進行數據檢驗, 預防后臺返回錯誤的字段等等。
通過上面這樣的分層,上面2個問題就迎刃而解了。
二、數據流網狀調用
我們對 Store 和 視圖進行了分層,但是隨著項目的迭代,又出現了下面這種情況,數據流呈網狀調用。
?
舉一個例子:對項目設置靜音
在舊的代碼中,工作站會調用到團隊中的數據,也會修改到團隊中的數據,甚至接口回調后,他還會對各個地方涉及到靜音界面的相關數據都進行修改。
這會帶來一個問題,由于是全局共用的 Redux ,我們在 A 地方調用這個方法,這里會更新 B 界面(B 地方并沒有出現在用戶的界面上),也就是說,修改 A 地方的 action ,還需要同時關注 B,C,D…… 這些地方(實際上,我們并不需要關注其他的地方)
我們知道分層可以解耦,對降低復雜度非常有效。那我們是否也可以對 Store 也進行分層,且約束他們之間互相調用?
架構演進:
?
重構后,同層級的 store 不能互相調用,若需要調用,則說明這個 store 要提升到上一層級(下文有詳細說明),換成這種方式之后,整個 Store 層也清晰明了,并且不會隨著業務迭代而變得越來越復雜。
重構后,我們只需要用下面短短幾行代碼就可以實現:
?
每個視圖組件(A/B/C/D)拿到調用“靜音”的回調結果,自己做更新界面的操作。
?
這樣子,由原來的一處代碼需要關注 N 處, 到現在只需要一處代碼關注一處,大大降低了代碼的復雜度。
三、數據全局化
舉一個例子:在非父子組件之間共享傳遞一些狀態,我們會使用狀態提升來解決這個問題。但是如果此時組件之間的嵌套過深,那么中間經過的組件都會幫忙傳遞這些無用的 props, 且如果需要傳遞參數或者增加 props ,都需要修改 A、B、中間組件 * n 個地方。
?
那么這時候,我們就會將這些狀態放到全局 redux 中。但這樣,又會引來其他的問題,對于一些臨時保持的狀態,比如在中后臺常見的場景:A 組件控制同層彈窗組件 E 的顯示隱藏狀態,而這些狀態對于用戶來說,并不需要保存,是一次性的。
?
此時由于中間跨越的組件過多,我們將他放到了 redux 里面去,久而久之,redux 中的 action 越來越多。慢慢的,我們每次修改都需要確認他是否也一樣影響了其他組件(實際上,這個 action 只會在這個模塊中使用到,其他模塊都無須關注)。
對于 Redux 派的數據流管理,都是中心化的。看了大部分的中后臺產品,需要全局共享的數據并不多,一般只有用戶 user 信息。在這個背景下,我們看向 mobx ,他天然支持多個 store 。那怎么去設計 store ?
高內聚對于提升項目的可維護性自然是一個好事,但是如果不控制好粒度,也容易引起問題。比如, 我們嚴格按照 React 官方的指導意見:如果多個 Component 之間要發生交互, 那么狀態(即: 數據) 就維護在這些 Component 的最小公約父節點上
?
那么按照這種劃分,我們的程序會出現成十上百個公約節點, 隨著項目的迭代前進,曾經只是 A, B 之間共享,后面變成了 A,B,C 共享, 最大公約節點又需要向上提升,在多人協作的情況下,太多的提升和變更很容易引起項目的不穩定,所以,我們劃分了下面三層 store ,最小粒度的 store 為模塊 store 。
?
映射到具體項目中的 Store 圖如下:
?
四、領域模型 DDD
我們如何去設計 Store ?我們使用了領域驅動設計,也就是 DDD 。
為何需要 DDD 呢?
- 在傳統的前端開發流程中,前端和業務是通過視覺稿來聯系的,一旦視覺稿發生變更,就意味大量的修改成本。目前產品到了 1~n 階段,視覺稿需求稿的變化是必然的。
- 一個復雜的產品,是由多人協作的,如果大家都是按照視覺稿去設計 Store ,那么重復的邏輯會越寫越多,后面技術債也越來越大。
- 貼合業務
如果我們采用 DDD ,比如我們抽象一個領域模型「文件」,在這里面,存放文件的相關操作:「修改文件名,刪除文件,移動文件…… 」,有了這樣一個穩定的領域模型,視圖層只需要實現視覺稿和組裝業務邏輯,具備很強的靈活性,就好像搭積木一樣,底層的領域模型不需要變動,只需要改動交互變更或視圖。極大提升了開發效率和維護性。
舉個例子,隨著項目的迭代,關于文件的相關操作:刪除、移動等這些已經沉淀在領域模型中了,如果此時產品變更,刪除的入口發生了變化,或者是增加了一個新的刪除入口,那我們只需要修改完視圖組件,然后在需要調用的地方調用下對應領域的 action 即可
按照領域的劃分, Store 之間的界限也更加清晰。
注意:DDD 不是一個框架,而是一種架構思想,所以前端在開發之前,要先細化需求,設計好 Store 再進行開發。
項目中已沉淀的領域
?
?
改進點:根據 Mobx 官方指導建議,除了頁面一些松散的狀態,還會根據整個業務特性沉淀一些通用的領域模型,這些可以根據不同頁面需要,注入到對應的 Page Store 中去。
?
領域驅動設計這個理念已經在我們的業務中摸打滾爬了幾個月,參與開發的同學都說好,在前端人力瓶頸大的情況下,后臺同學也參與進來寫 Store ,Store 與 視圖同步并行開發,從串行開發到并行,極大提升了整體的開發效率。
技術選型
寫了這么多,新的系統架構需要以下幾個特性,才能讓系統走得更快更遠:
- 合理的分層, UI / 邏輯分離
- 復雜項目 Store 的粒度細化很重要, 領域模型 DDD
- 拒絕模版代碼,提升開發效率
- 面向未來&兼容舊代碼 -- 支持 hook & class
- 更好的 ts 支持
我們期望擁有這些特性,然后一個一個對比。
unstated-next
redux
為什么選擇 mobx ?
?少量/無 模版代碼
?面向未來&兼容舊代碼 -- 支持 hook & class
?很方便地對業務進行分層,很方便地設計領域模型
?TS 支持 0 成本
?天然支持多態 Store ,去中心化更方便
?需要顯示 DI, 解決了網狀調用的問題
關于 Mobx 的缺點業界也說了很多,無非就是以下幾個點:
綜合上面種種原因,如果為了這些特性,重新去造一個輪子或者改造一個輪子,成本遠遠比直接借助 mobx 的力量更大。所以,在調研了多種數據流方案之后,選擇了 Mobx 來支撐我們上面的架構。
總結
沒有最好的技術方案,只有最適合業務的技術方案。我們從一個一個“業務痛點”推導出一套解決方案,并且已在實際項目中跑了幾個月,也獲得了不錯的效果。
作者:被單
原文鏈接
本文為阿里云原創內容,未經允許不得轉載
總結
以上是生活随笔為你收集整理的这可能是大型复杂项目下数据流的最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一文教你轻松搞定ANR异常捕获与分析方法
- 下一篇: 专访涯海:阿里云中间件是如何支撑双11的