如何管理好10萬行代碼的前端單頁面應用

做者簡介 導演 螞蟻金服·數據體驗技術團隊前端

螞蟻金服數據平臺前端團隊主要負責多個數據相關的PC Web單頁面應用程序,業務複雜度類比Excel等桌面應用,業務前端代碼量在幾萬行~幾十萬行,隨着產品不斷完善,破百萬指日可待。管理好10萬行級甚至百萬行級代碼的前端應用,是咱們團隊的核心挑戰之一。react

接下來的系列文章,我會嘗試從如下幾個角度介紹咱們團隊應對挑戰的方法:git

  • 前端架構
  • 質量保障
  • 性能優化
  • 團隊前端開發流程
  • 人員素養

前端架構

團隊的架構方案是多個產品經歷一年的持續迭代,不斷摸索出來的一套適合本團隊數據產品業務場景的架構方案,架構方案中還存在還沒有解決的痛點和有爭議的部分須要持續優化,不保證這套架構適合您的產品。github

產品特色

先介紹下咱們團隊的產品特色:數據庫

  • ToB產品,業務複雜度高、業務理解門檻高;
  • 前端代碼量巨大(數據分析產品從零開始經歷8個月迭代業務代碼8萬行,僅實現了產品長期規劃需求的20%)

架構方案

架構的目的是管理複雜度,將複雜問題分而治之、有效管理,咱們的具體方法以下:redux

1. 首先經過路由切割「頁面級」粒度的功能模塊

這裏的「頁面級」粒度指一個路由映射的組件設計模式

router

2. 同一「頁面」內的模塊再劃分

劃分原則:api

  • 縱向:經過業務功能(可根據視圖模塊判斷)劃分
  • 橫向:經過Model-View-Controller三種不一樣職能劃分

module

3. 合併同類項

繼續細分粒度,而後將可複用模塊或組件抽離到公共區域性能優化

3.1 數據模型

數據模型根據職責分紅兩類:架構

  1. Domain Model 領域模型
  2. 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 視圖層組件

組件根據職責劃分爲兩類:

  1. Container Component 容器型組件
  2. 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.randomnew 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完成的:

  • <Provider> 組件經過context屬性向後代<connect>組件提供(provide)store對象;
  • <connect> 是一個高階組件,做用是將store與view層組件鏈接起來(這裏重複提一句,redux官方將<connect>直接鏈接的組件定義爲container component),<connect>向開發者開放了幾個回調函數鉤子(mapStateToProps, mapDispatchToProps...)用於自定義注入container component的props的姿式;
  • react-redux監聽redux store的變動,store改變後通知每個connect組件刷新本身和後代組件,爲了減小沒必要要的刷新提高性能,connect實現了shouldComponentUpdate方法,若是props不變的話,不刷新connect包裹的container component;

總結

嚴格遵循架構規範和單向數據流規範,能夠保證咱們的前端應用在比較粗的粒度上的可維護性和擴展性,對於更細的粒度的代碼,咱們組織童鞋學習和分享《設計模式》《重構 - 改善既有代碼的設計》,持續打磨和優化本身的代碼,將來團隊會持續輸出這方面的系列文章。

本篇先聊前端通用架構,具體模塊的業務架構、架構遵循的原則、團隊架構組的架構評審流程等內容會在接下來的系列文章中闡述。感興趣的同窗關注專欄或者發送簡歷至 'tao.qit####alibaba-inc.com'.replace('####', '@'),歡迎有志之士加入~

原文地址:github.com/ProtoTeam/b…

相關文章
相關標籤/搜索