給2019前端的5個建議
2019 農曆新年即將到來,是時候總結一下團隊過去一年的技術沉澱。過去一年我們支撐的數據相關業務突飛猛進,其中兩個核心平台級產品代碼量分別達到30+萬行和80+萬行,TS 模塊數均超過1000個,協同開發人員增加到20+人。由於歷史原因,開發框架同時基於React 和Angular,考慮到產品的複雜性、人員的短缺和技術背景各異,我們嘗試了各種方法打磨工具體係來提升開發效率,以下是節選的5項主要方法。
一、基於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 三原則:
- Single source of truth(組件Stateless,數據來源於Store)
- State is read-only(只能通過觸發action 來改變State)
- Changes are made with pure functions(Reducer 是純函數)
這三個問題我們是通過自研iron-redux庫來解決。
最終我們得到如下扁平的狀態樹。雖龐大但有序,你可以快速而明確的訪問任何數據。

如何減少樣板代碼?
使用原生Redux,一個常見的請求處理如下。非常冗餘,這是Redux 被很多人詬病的原因
constinitialState={loading=true,error=false,data=[]};functiontodoApp(state=initialState,action){switch(action.type){caseDATA_LOADING:return{...state,loading:true,error:false}caseDATA_SUCCESS:return{...state,loading:false,data:action.payload}caseDATA_ERROR:return{...state,loading:false,error:true}default:returnstate}}
使用iron-redux 後:
classInitialState{data=newAsyncTuple(true);}functionreducer(state=newInitialState(),action){switch(action.type){/** 省略其它 action 处理 */default:returnAsyncTuple.handleAll(prefix,state,action);}}
代碼量減少三分之二! !
主要做了這2點:
- 引入了預設的
AsyncTuple
類型,就是{data: [], loading: boolean, error: boolean}
這樣的數據結構; - 使用
AsyncTuple.handleAll
處理LOADING/SUCCESS/ERROR這3種action,handleAll的代碼很簡單,使用if判斷action.type的後綴即可,源碼在這裡。
曾經React 和Angular 是兩個很難調和的框架,開發中浪費了我們大量的人力。通過使用輕量級的iron-redux,完全遵循Redux核心原則下,我們內部實現了除組件層以外幾乎所有代碼的複用。開發規範、工具庫達成一致,開發人員能夠無縫切換,框架差異帶來的額外成本降到很低。
二、全面擁抱TypeScript
TypeScript目前可謂大紅大紫,根據2018 stateofjs ,超過50%的使用率以及90%的滿意度,甚至連Jest也正在從Flow切換到TS 。如果你還沒有使用,可以考慮切換,絕對能給項目帶來很大提升。過去一年,我們從部分使用TS 變為全面切換到TS,包括我們自己開發的工具庫等。
TS 最大的優勢是它提供了強大的靜態分析能力,結合TSLint 能對代碼做到更加嚴格的檢查約束。傳統的EcmaScript 由於沒有靜態類型,即使有了ESLint 也只能做到很基本的檢查,一些typo 問題可能線上出了Bug 後才被發現。
下圖是一個前端應用常見的4層架構。代碼和工俱全面擁抱TS後,實現了從後端API接口到View組件的全鏈路靜態分析,具有了完善的代碼提示和校驗能力。

除了上面講的iron-redux,我們引入Pont 實現前端取數,它可以自動把後端API 映射到前端可調用的請求方法。
Pont實現原理:
Pont (法語:橋)是我們研發的前端取數層框架。對接的後端API 使用Java Swagger,Swagger 能提供所有API 的元訊息,包括請求和響應的類型格式。 Pont 解析API 元訊息生成TS 的取數函數,這些取數函數類型完美,並掛載到API 模塊下。最終代碼中取數效果是這樣的:

Pont實現的效果有:
- 根據方法名自動匹配url、method,並且對應到prams、response 類型完美,並能自動提示
- 後端API 接口變更後,前端相關聯的請求會自動報錯,再也不擔心後端悄悄改接口前端不知曉
- 再也不需要前後端接口約定文檔,使用代碼保證前端取數和後端接口定義完全一致
另外iron-redux 能接收到Pont 接口響應數據格式,並推導出整個Redux 狀態樹的靜態類型定義,Store 中的數據完美的類型提示。效果如下:

最終TS讓代碼更加健壯,尤其是對於大型項目,編譯通過幾乎就代表運行正常,也給重構增加了很多信心。
三、回歸Sass/Less
2015 年我們就開始實踐CSS Modules,包括後來的styled-components 等,到2019 年css-in-js 方案依舊爭論不休,雖然它確實解決了一些CSS 語言天生的問題,但同時增加了不少成本,新手不夠友好、全局樣式覆蓋成本高漲、偽類處理複雜、與AntD等組件庫結合有坑。與此同時Sass/Less社區也在飛速發展,尤其是Stylelint的成熟,可以通過技術約束的手段來避免CSS的Bad Parts。
- 全局污染:約定每個樣式文件只能有一個頂級類,如
.home-page{ .top-nav {/**/}, .main-content{ /**/ } }
。如果有多個頂級類,可以使用Stylelint rule 檢測並給出警告。 - 依賴管理不徹底。借助webpack 的css-loader,已夠用。
- JS 和CSS 變量共享。關於JS 和Sass/Less 變量共享,我們摸索出了自己的解法:
// src/styles/variables.js module . exports = { // 主颜色'primary-color' : '#0C4CFF' , // 出错颜色'error-color' : '#F15533' , // 成功颜色'success-color' : '#35B34A' , };
以下為Webpack 配置,注入變量到Scss
// webpack.config.jsconststyleVariables=require('src/styles/variables');// ...{test:/.scss$/,use:['style-loader','css-loader?sourceMap&minimize',{loader:'sass-loader',options:{data:Object.keys(styleVariables).map(key=>`$${key}:${styleVariables[key]};`).join('n'),sourceMap:true,sourceMapContents:true}}]}//...
在scss 文件中,可以自己引用變量
// page.scss .button { background : $ primary-color ; }
四、開發工具覆蓋全鏈路
2019 年,你幾乎不可能再開發出React/Angular/Vue 級別的框架,也沒必要再造Ant-Design/Fusion-Design/Ng-Zorro 這樣的輪子。難道就沒有機會了嗎?
當然有,結合你自身的產品開發流程,依舊有很多機會。下面是常規項目的開發流程圖,任何一個環節只要深挖,都有提升空間。如果你能通過工具減少一個或多個環節,帶來的價值更大。

單拿其中的『開發』環節展開,就有很多可擴展的場景:

一個有代表性的例子是,我們開發了國際化工具kiwi 。它同樣具有TS 的類型完美,非常強大的文案提示,另外還有:
- VS Code插件kiwi linter ,自動對中文文案標紅,如果已有翻譯文案能自動完成替換
- Shell 命令全量檢查出沒有翻譯的文案,批量提交給翻譯人員
- Codemod 腳本自動實現舊的國際化方案向Kiwi 遷移,成本級低
除了以上三點,未來還計劃開發瀏覽器插件來檢查漏翻文案,利用Husky 在git 提交前對漏翻文案自動做機器翻譯等等。
未來如果你只提供一個代碼庫,那它的價值會非常局限。你可以參照上面的圖表,開發相應的擴展來豐富生態。如果你是新手,推薦學習下編譯原理和對應的擴展開發規範。
五、嚴格徹底的Code Review
過去的一年,我們一共進行了1200+ 多次Code Review(CR),很多同事從剛開始不好意思提MR 到後來追著別人Review,CR 成為每個人的習慣。通過CR 讓項目中任何一行代碼都至少被兩人觸達過,減少了絕大多數的低級錯誤,提升了代碼質量,這也是幫助新人成長最快的方式之一。

Code Review的幾個技巧:
- No magic
- Explicit not implicit
- 覆蓋度比深度重要,覆蓋度追求100%
- 頻率比儀式感重要,坐公交蹲廁所打開手機都可以Review 別人代碼,不需要專門組織會議
- 粒度要盡可能小,一個組件一個方法均可,可以結合Git Flow
- 24h 小時內處理,無問題直接merge,有問題一定要留comment,並且提供action
- 對於亟待上線來不及Review 的代碼,可以先合併上線,上線後再補充Review
- 需要自上而下的推動,具有完善的規範,同時定期總結Review 經驗來豐富開發規範
- CR 並不只是為了找錯,看到好的代碼,不要吝嗇你的讚美
- 本質是鼓勵開發者間更多的溝通,互相學習,營造技術文化氛圍
總結
以上5點當然不是我們技術的全部。除此之外我們還實踐了移動端開發、可視化圖表/WebGL、Web Worker、GraphQL、性能優化等等,但這些還停留在術的層面,未來到一定程度會拿出來分享。
如果你也準備或正在開發複雜的前端應用,同時團隊人員多樣技術背景各異,可以參考以上5點,使用Redux 實現規范清晰可預測的狀態管理,深耕TypeScript 來提升代碼健壯性和可維護性,借助各種Lint 工具回歸簡單方便的CSS,不斷打磨自己的開發工具來保證開發規範高效,並嚴格徹底實行Code Review 促進人的交流和提升。
Links
- Pont: nefe/pont
- Kiwi: nefe/kiwi
- iron-redux: nefe/iron-redux
- The State of JavaScript 2018
我們還在招聘,歡迎郵件簡歷,來信必回。 shaoyin.ssy @ http:// alibaba-inc.com