同樣做前端,為何差距越來越大?

阿里技術發表於2019-03-07

同樣做前端,為何差距越來越大?過去一年,阿里巴巴新零售事業群支撐的資料相關業務突飛猛進,其中兩個核心平臺級產品程式碼量急速增長,協同開發人員增加到數十人。

由於歷史原因,開發框架同時基於 React 和 Angular,考慮到產品的複雜性、人員的短缺和技術背景各異,我們嘗試了各種方法打磨工具體系來提升開發效率,以下分享五點。

同樣做前端,為何差距越來越大?

一、基於 Redux 的狀態管理

從2013年React釋出至今已近6個年頭,前端框架逐漸形成 React/Vue/Angular 三足鼎立之勢。幾年前還在爭論單向繫結和雙向繫結孰優孰劣,現在三大框架已經不約而同選擇單向繫結,雙向繫結淪為單純的語法糖。框架間的差異越來越小,加上 Ant-Design/Fusion-Design/NG-ZORRO/ElementUI 元件庫的成熟,選擇任一你熟悉的框架都能高效完成業務。

那接下來核心問題是什麼?我們認為是狀態管理。簡單應用使用元件內 State 方便快捷,但隨著應用複雜度上升,會發現資料散落在不同的元件,元件通訊會變得異常複雜。我們先後嘗試過原生 Redux、分形 Fractal 的思路、自研類 Mobx 框架、Angular Service,最終認為 Redux 依舊是複雜應用資料流處理最佳選項之一。

慶幸的是除了 React 社群,Vue 社群有類似的 Vuex,Angular 社群有 NgRx 也提供了幾乎同樣的能力,甚至 NgRx 還可以無縫使用 redux-devtools 來除錯狀態變化。

同樣做前端,為何差距越來越大?

無論如何優化,始終要遵循 Redux 三原則:

同樣做前端,為何差距越來越大?

這三個問題我們是通過自研 iron-redux 庫【1】來解決,以下是背後的思考:

如何組織 Action?

  1. action type 需要全域性惟一,因此我們給 action type 新增了 prefix,其實就是 namespace 的概念;

  2. 為了追求體驗,請求(Fetch)場景需要處理 3 種狀態,對應 LOADING/SUCCESS/ERROR 這 3 個action,我們通過 FetchTypes 型別來自動生成對應到 3 個 action。

如何組織 Store/Reducer?

  1. reducer 和 view 不必一一對應,應用中同時存在元件樹和狀態樹,按照各自需要去組織,通過 connect 來繫結狀態樹的一個或多個分支到元件樹;

  2. 通過構造一些預設資料型別來減少樣板程式碼。對於 Fetch 返回的資料我們定義了 AsyncTuple 這種型別,減少了樣板程式碼;

  3. 明確的組織結構,第1層是 ROOT,第2層是各個頁面,第3層是頁面內的卡片,第4層是卡片的資料,這樣劃分最深處基本不會超過5層。

最終我們得到如下扁平的狀態樹。雖龐大但有序,你可以快速而明確的訪問任何資料。

同樣做前端,為何差距越來越大?

Redux 狀態樹

如何減少樣板程式碼?

使用原生 Redux,一個常見的請求處理如下。非常冗餘,這是 Redux 被很多人詬病的原因:同樣做前端,為何差距越來越大?使用 iron-redux 後:同樣做前端,為何差距越來越大?程式碼量減少三分之二!!

主要做了這2點:

  1. 引入了預設的 AsyncTuple 型別,就是 {data: [], loading: boolean, error: boolean}  這樣的資料結構;

  2. 使用 AsyncTuple.handleAll  處理 LOADING/SUCCESS/ERROR 這 3 種 action,handleAll 的程式碼很簡單,使用 if 判斷 action.type 的字尾即可,原始碼【2】。

曾經 React 和 Angular 是兩個很難調和的框架,開發中浪費了我們大量的人力。通過使用輕量級的 iron-redux,完全遵循 Redux 核心原則下,我們內部實現了除元件層以外幾乎所有程式碼的複用。開發規範、工具庫達成一致,開發人員能夠無縫切換,框架差異帶來的額外成本降到很低。

二、全面擁抱 TypeScript

TypeScript 目前可謂大紅大紫,根據 2018 stateofjs【3】,超過 50% 的使用率以及 90% 的滿意度,甚至連 Jest 也正在從 Flow 切換到 TS【4】。如果你還沒有

使用,可以考慮切換,絕對能給專案帶來很大提升。過去一年,我們從部分使用 TS 變為全面切換到 TS,包括我們自己開發的工具庫等。

TS 最大的優勢是它提供了強大的靜態分析能力,結合 TSLint 能對程式碼做到更加嚴格的檢查約束。傳統的 EcmaScript 由於沒有靜態型別,即使有了 ESLint 也只能做到很基本的檢查,一些 typo 問題可能線上出了 Bug 後才被發現。

下圖是一個前端應用常見的4層架構。 程式碼和工具全面擁抱 TS 後,實現了從後端 API 介面到 View 元件的全鏈路靜態分析,具有了完善的程式碼提示和校驗能力。

同樣做前端,為何差距越來越大?

前後端協作簡圖

除了上面講的 iron-redux,我們還引入 Pont 【5】實現前端取數,它可以自動把後端 API 對映到前端可呼叫的請求方法。

Pont 實現原理:(法語:橋) 是我們研發的前端取數層框架。對接的後端 API 使用 Java Swagger,Swagger 能提供所有 API 的元資訊,包括請求和響應的型別格式。Pont 解析 API 元資訊生成 TS 的取數函式,這些取數函式型別完美,並掛載到 API 模組下。最終程式碼中取數效果是這樣的:

同樣做前端,為何差距越來越大?

Pont 實現的效果有:

  1. 根據方法名自動匹配 url、method,並且對應到 prams、response 型別完美,並能自動提示;

  2. 後端 API 介面變更後,前端相關聯的請求會自動報錯,再也不擔心後端悄悄改介面前端不知曉;

  3. 再也不需要前後端介面約定文件,使用程式碼保證前端取數和後端介面定義完全一致。

另外 iron-redux 能接收到 Pont 介面響應資料格式,並推匯出整個 Redux 狀態樹的靜態型別定義,Store 中的資料完美的型別提示。效果如下:

同樣做前端,為何差距越來越大?

最終 TS 讓程式碼更加健壯,尤其是對於大型專案,編譯通過幾乎就代表執行正常,也給重構增加了很多信心。

三、迴歸 Sass/Less

2015 年我們就開始實踐 CSS Modules,包括後來的 styled-components 等,到 2019 年 css-in-js 方案依舊爭論不休,雖然它確實解決了一些 CSS 語言天生的問題,但同時增加了不少成本,新手不夠友好、全域性樣式覆蓋成本高漲、偽類處理複雜、與AntD等元件庫結合有坑。與此同時 Sass/Less 社群也在飛速發展,尤其是 Stylelint 【6】的成熟,可以通過技術約束的手段來避免 CSS 的 Bad Parts。

  1. 全域性汙染:約定每個樣式檔案只能有一個頂級類,如 .home-page{ .top-nav {/<strong>/}, .main-content{ /</strong>/ } }。如果有多個頂級類,可以使用 Stylelint rule 檢測並給出警告。

  2. 依賴管理不徹底。藉助 webpack 的 css-loader,已夠用。

  3. JS 和 CSS 變數共享。關於 JS 和 Sass/Less 變數共享,我們摸索出了自己的解法:
    同樣做前端,為何差距越來越大?

在 scss 檔案中,可以直接引用變數:同樣做前端,為何差距越來越大?四、開發工具覆蓋全鏈路

2019 年,你幾乎不可能再開發出 React/Angular/Vue 級別的框架,也沒必要再造 Ant-Design/Fusion-Design/Ng-Zorro 這樣的輪子。難道就沒有機會了嗎?

當然有,結合你自身的產品開發流程,依舊有很多機會。下面是常規專案的開發流程圖,任何一個環節只要深挖,都有提升空間。如果你能通過工具減少一個或多個環節,帶來的價值更大。

同樣做前端,為何差距越來越大?

單拿其中的【開發】環節展開,就有很多可擴充套件的場景:

同樣做前端,為何差距越來越大?

一個有代表性的例子是,我們開發了國際化工具 kiwi【7】。它同樣具有 TS 的型別完美,非常強大的文案提示,另外還有:

  1. VS Code 外掛 kiwi linter【8】,自動對中文文案標紅,如果已有翻譯文案能自動完成替換;

  2. Shell 命令全量檢查出沒有翻譯的文案,批量提交給翻譯人員;

  3. Codemod 指令碼自動實現舊的國際化方案向 Kiwi 遷移,成本極低。

除了以上三點,未來還計劃開發瀏覽器外掛來檢查漏翻文案,利用 Husky 在 git 提交前對漏翻文案自動做機器翻譯等等。

未來如果你只提供一個程式碼庫,那它的價值會非常侷限。你可以參照上面的圖表,開發相應的擴充套件來豐富生態。如果你是新手,推薦學習下編譯原理和對應的擴充套件開發規範。

五、嚴格徹底的 Code Review

過去的一年,我們一共進行了 1200+ 多次 Code Review(CR),很多同事從剛開始不好意思提 MR(GitLab Merge Request,Code Review 的一種方式) 到後來追著別人 Review,CR 成為每個人的習慣。通過 CR 讓專案中任何一行程式碼都至少被兩人觸達過,減少了絕大多數的低階錯誤,提升了程式碼質量,這也是幫助新人成長最快的方式之一。

同樣做前端,為何差距越來越大?

[其中一個專案MR截圖]

Code Review 的幾個技巧:

  1. No magic;

  2. Explicit not implicit;

  3. 覆蓋度比深度重要,覆蓋度追求100%;

  4. 頻率比儀式感重要,坐公交蹲廁所開啟手機都可以 Review 別人程式碼,不需要專門組織會議;

  5. 粒度要儘可能小,一個元件一個方法均可,可以結合 Git Flow;

  6. 24h 小時內處理,無問題直接 merge,有問題一定要留 comment,並且提供 action;

  7. 對於亟待上線來不及 Review 的程式碼,可以先合併上線,上線後再補充 Review;

  8. 需要自上而下的推動,具有完善的規範,同時定期總結 Review 經驗來豐富開發規範;

  9. CR 並不只是為了找錯,看到好的程式碼,不要吝嗇你的讚美;

  10. 本質是鼓勵開發者間更多的溝通,互相學習,營造技術文化氛圍。

總結

以上5點當然不是我們技術的全部。除此之外我們還實踐了移動端開發、視覺化圖表/WebGL、Web Worker、GraphQL、效能優化等等,但這些還停留在術的層面,未來到一定程度會拿出來分享。

如果你也準備或正在開發複雜的前端應用,同時團隊人員多樣技術背景各異,可以參考以上5點,使用 Redux 實現規範清晰可預測的狀態管理,深耕 TypeScript 來提升程式碼健壯性和可維護性,藉助各種 Lint 工具迴歸簡單方便的 CSS,不斷打磨自己的開發工具來保證開發規範高效,並嚴格徹底實行 Code Review 促進人的交流和提升。

最後,阿里資料技術及產品部(DT)正在招聘前端開發工程師,這裡有豐富海量的資料和前端應用場景,如果你對文中提到的5點深有感觸,渴望快速成長,歡迎和我聯絡:shaoyin.ssy@alibaba-inc.com

參考資料:

1:https://github.com/nefe/iron-redux

2.https://github.com/nefe/iron-redux/blob/master/index.ts#L288

3.https://2018.stateofjs.com/javascript-flavors/overview/

4.https://github.com/facebook/jest/pull/7554

5.https://github.com/nefe/pont

6.https://stylelint.io/

7.https://github.com/nefe/kiwi

8.https://marketplace.visualstudio.com/items?itemName=undefinedvs.vscode-i18n-linter

相關文章