如何管理好10萬行代碼的前端單頁面應用
作者簡介 導演 螞蟻金服數據前端
螞蟻金服數據平台前端團隊主要負責多個數據相關的PC Web單頁面應用程序,業務複雜度類比Excel等桌面應用,業務前端代碼量在幾萬行~幾十萬行,隨著產品不斷完善,破百萬指日可待。管理好10萬行級甚至百萬行級代碼的前端應用,是我們團隊的核心挑戰之一。
接下來的系列文章,我會嘗試從以下幾個角度介紹我們團隊應對挑戰的方法:
- 前端架構
- 質量保障
- 性能優化
- 團隊前端開發流程
- 人員素養
前端架構
團隊的架構方案是多個產品經歷一年的持續迭代,不斷摸索出來的一套適合本團隊數據產品業務場景的架構方案,架構方案中還存在尚未解決的痛點和有爭議的部分需要持續優化,不保證這套架構適合您的產品。
產品特點
先介紹下我們團隊的產品特點:
- ToB產品,業務複雜度高、業務理解門檻高;
- 前端代碼量巨大(數據分析產品從零開始經歷8個月迭代業務代碼8萬行,僅實現了產品長期規劃需求的20%)
架構方案
架構的目的是管理複雜度,將複雜問題分而治之、有效管理,我們的具體方法如下:
1. 首先通過路由切割「頁面級」粒度的功能模塊
這裡的「頁面級」粒度指一個路由映射的組件
router
2. 同一「頁面」內的模塊再劃分
劃分原則:
- 縱向:通過業務功能(可根據視圖模塊判斷)劃分
- 橫向:通過Model-View-Controller三種不同職能劃分
module
3. 合併同類項
繼續細分粒度,然後將可復用模塊或組件抽離到公共區域
3.1 數據模型
數據模型根據職責分成兩類:
- Domain Model 領域模型
- App State Modal 應用狀態模型
3.1.1 領域模型
領域模型是業務數據,往往要持久化到資料庫或localStorage中,屬於可跨模塊復用的公共數據,如:
- Users 用戶信息
- Datasets 數據集信息
- Reports 報表信息
領域模型作為公共數據,建議統一存放在一個叫做Domain Model Layer的架構獨立分層中(前端業界一般對這層的命名為ORM層)。
下沉到Domain Model Layer(領域模型層)有諸多利處:
- 跨模塊數據同步問題不復存在,例如:之前Users對象在A和B兩個業務模塊中單獨存儲,A模塊變更Users對象後,需將Users變更同步到B模塊中,如不同步,A、B模塊在界面上呈現的User信息不一致,下沉到領域模型層統一管理後,問題不復存在;
- 除領域模型復用外,還可復用領域模型相關的CRUD Reducer,例如:之前Users對象對應的Create Read Update Delete方法可能在A和B兩個業務模塊各維護一套,下沉到領域模型層統一管理後,減少了代碼重複問題;
- 自然承擔了部分跨模塊通信職責,之前數據同步相關的跨模塊通信代碼沒有了存在的必要性;
3.1.2 應用狀態模型
應用狀態模型是與視圖相關的狀態數據,如:
- 當前頁面選中了列表的第n行 currentSelectedRow: someId
- 窗口是否處於打開狀態 isModalShow: false
- 某種視圖元素是否在拖拽中 isDragging: true
這些數據與具體的視圖模塊或業務功能強相關,建議存放在業務模塊的Model中。
3.2 視圖層組件
組件根據職責劃分為兩類:
- Container Component 容器型組件
- Presentational Component 展示型組件
3.2.1 容器型組件
容器型組件是與store直連的組件,為展示型組件或其它容器組件提供數據和行為,盡量避免在其中做一些界面渲染相關的事情。
3.2.2 展示型組件
展示型組件獨立於應用的其它部分內容,不關心數據的載入和變更,保持職責單一,僅做視圖呈現和最基本交互行為,通過props接收數據和回調函數輸出結果,保證接收的數據為組件數據依賴的最小集。
一個有成百上千展示型組件的複雜系統,如果展示型組件粒度切分能很好的遵循高內聚低耦合和職責單一原則的話,可以沉澱出很多可復用的通用業務組件。
3.3 公共服務
- 所有的HTTP請求放在一起統一管理;
- 日誌服務、本地存儲服務、錯誤監控、Mock服務等統一存放在公共服務層;
按照上面三點合併同類項後,業務架構圖變更為
api
4. 跨模塊通信
模塊粒度逐漸細化,會帶來更多的跨模塊通信訴求,為避免模塊間相互耦合、確保架構長期乾淨可維護,我們規定:
- 不允許在一個模塊內部直接調用其他模塊的Dispatch方法(寫操作、變更其他模塊的state)
- 不允許在一個模塊內部直接讀取其他模塊的state方法(讀操作)
我們建議將跨模塊通信的邏輯代碼放在父模塊中,或者在一個叫做Mediator層中單獨維護。
最終得到我們團隊完整的業務邏輯架構圖:
Architecture
數據流管理
剛剛從空間維度講了架構管理的方案,現在從時間維度說說應用的數據流轉 --- Redux單向數據流。
Redux架構的設計核心是單向數據流,應用中所有的數據都應該遵循相同的生命周期,確保應用狀態的可預測性。
redux
1. Action
- 用戶操作行為:click drag input ...
- 服務端返回數據後續的行為
2. Reducer
每個Action都會對應一個數據處理函數,即Reducer。特彆強調,Reducer必須是純函數(pure function),這個規定帶來一個非常大的好處,數據處理層代碼變的非常容易寫單元測試。
純函數的特徵是入參相同的情況下,返回值恆等,舉個栗子??:
純函數:
function add(a, b) { return a + b;}
非純函數:
function now() { let now = new Date(); return now;}
函數中如果包含 Math.random,new Date(), 非同步請求等內容,且影響到最終結果的返回,即為非純函數。
3. Store
Store 數據存放的地方,store保存從進入頁面開始所有Action操作生成的數據狀態(state),每次Action引發的數據變更都必須生成一個新的state對象,且確保舊的state對象不被修改。這樣做可以保證
應用的狀態的可預測、可追溯,也方便設計Redo/Undo功能。我們團隊使用輕量級的immutable方案immutability-helper,相比完全拷貝一份(deep clone)性能更優、存儲空間利用率更高。
immutability-helper
immutability-helper的API不夠友好,我們寫了一個庫immutability-helper-x增強它的易用性。
immutability-helper API風格:
import update from "immutability-helper";const newData = update(myData, { x: { y: { z: { $set: 7 } } },});
immutability-helper-x API風格:
import update from "immutability-helper-x";const newData = update.$set(myData, "x.y.z", 7);
4. 統一渲染視圖
React/Redux是一種典型的數據驅動的開發框架(Data-Driven-Development),在開發中,我們可以將更多的精力集中在數據(領域模型+狀態模型)的操作和流轉上,再也不用被各種繁瑣的DOM操作代碼困擾,當Store變更時,React/Redux框架會幫助我們自動的統一渲染視圖。
監聽Store變更刷新視圖的功能是由react-redux完成的:
- 組件通過context屬性向後代組件提供(provide)store對象;
- 是一個高階組件,作用是將store與view層組件連接起來(這裡重複提一句,redux官方將直接連接的組件定義為container component),向開發者開放了幾個回調函數鉤子(mapStateToProps, mapDispatchToProps...)用於自定義注入container component的props的姿勢;
- react-redux監聽redux store的變更,store改變後通知每一個connect組件刷新自己和後代組件,為了減少不必要的刷新提升性能,connect實現了shouldComponentUpdate方法,如果props不變的話,不刷新connect包裹的container component;
總結
嚴格遵循架構規範和單向數據流規範,可以保證我們的前端應用在比較粗的粒度上的可維護性和擴展性,對於更細的粒度的代碼,我們組織童鞋學習和分享《設計模式》 和 《重構 - 改善既有代碼的設計》,持續打磨和優化自己的代碼,未來團隊會持續輸出這方面的系列文章。
本篇先聊前端通用架構,具體模塊的業務架構、架構遵循的原則、團隊架構組的架構評審流程等內容會在接下來的系列文章中闡述。感興趣的同學關注專欄或者發送簡歷至 tao.qit###http://alibaba-inc.com,歡迎有志之士加入~
原文地址:如何管理好10萬行代碼的前端單頁面應用 - 掘金
推薦閱讀:
※眾籌排行榜:現實巴波 ARM ?一個小球球居然有三種形態!
※如何評價聯想 moto z 2018?
TAG:前端架构 | SPASingle-PageApp | 模块化 |