2019給前端的5個建議
一、基于 Redux 的狀態(tài)管理
從2013年React發(fā)布至今已近6個年頭,前端框架逐漸形成 React/Vue/Angular 三足鼎立之勢。幾年前還在爭論單向綁定和雙向綁定孰優(yōu)孰劣,現(xiàn)在三大框架已經(jīng)不約而同選擇單向綁定,雙向綁定淪為單純的語法糖??蚣荛g的差異越來越小,加上 Ant-Design/NG-ZORRO/ElementUI 組件庫的成熟,選擇任一你熟悉的框架都能高效完成業(yè)務(wù)。
那接下來核心問題是什么?我們認為是狀態(tài)管理。簡單應用使用組件內(nèi) State 方便快捷,但隨著應用復雜度上升,會發(fā)現(xiàn)數(shù)據(jù)散落在不同的組件,組件通信會變得異常復雜。我們先后嘗試過原生 Redux、分形 Fractal 的思路、自研類 Mobx 框架、Angular Service,最終認為 Redux 依舊是復雜應用數(shù)據(jù)流處理最佳選項之一。
慶幸的是除了 React 社區(qū),Vue 社區(qū)有類似的 Vuex,Angular 社區(qū)有 NgRx 也提供了幾乎同樣的能力,甚至 NgRx 還可以無縫使用 redux-devtools 來調(diào)試狀態(tài)變化。

無論如何優(yōu)化,始終要遵循 Redux 三原則:
原則 | 方法 | 引發(fā)的問題 |
---|---|---|
Single source of truth | 組件 Stateless,數(shù)據(jù)來源于 Store | 如何組織 Store? |
State is read-only | 只能通過觸發(fā) action 來改變 State | action 數(shù)量膨脹,大量樣板代碼 |
Changes are made with pure functions | Reducer 是純函數(shù) | 副作用如何處理,大量樣板代碼 |
這三個問題我們是通過自研 iron-redux 庫來解決,以下是背后的思考:
如何組織 Action?
action type 需要全局惟一,因此我們給 action type 添加了 prefix,其實就是 namespace 的概念
為了追求體驗,請求(Fetch)場景需要處理 3 種狀態(tài),對應 LOADING/SUCCESS/ERROR 這 3 個action,我們通過 FetchTypes 類型來自動生成對應到 3 個 action
如何組織 Store/Reducer?
- reducer 和 view 不必一一對應,應用中同時存在組件樹和狀態(tài)樹,按照各自需要去組織,通過 connect 來綁定狀態(tài)樹的一個或多個分支到組件樹
- 通過構(gòu)造一些預設(shè)數(shù)據(jù)類型來減少樣板代碼。對于 Fetch 返回的數(shù)據(jù)我們定義了 AsyncTuple 這種類型,減少了樣板代碼
- 明確的組織結(jié)構(gòu),第1層是 ROOT,第2層是各個頁面,第3層是頁面內(nèi)的卡片,第4層是卡片的數(shù)據(jù),這樣劃分最深處基本不會超過5層
最終我們得到如下扁平的狀態(tài)樹。雖龐大但有序,你可以快速而明確的訪問任何數(shù)據(jù)。
如何減少樣板代碼?
使用原生 Redux,一個常見的請求處理如下。非常冗余,這是 Redux 被很多人詬病的原因。
- const initialState = {
- loading = true,
- error = false,
- data = []};function todoApp(state = initialState, action) {
- switch (action.type) {
- case DATA_LOADING:
- return {
- ...state,
- loading: true,
- error: false
- }
- case DATA_SUCCESS:
- return {
- ...state,
- loading: false,
- data: action.payload
- }
- case DATA_ERROR:
- return {
- ...state,
- loading: false,
- error: true
- }
- default:
- return state
- }}
使用 iron-redux 后:
- class InitialState {
- data = new AsyncTuple(true);}function reducer(state = new InitialState(), action) {
- switch (action.type) {
- /** 省略其它 action 處理 */
- default:
- return AsyncTuple.handleAll(prefix, state, action);
- }}
代碼量減少三分之二!!
主要做了這2點:
- 引入了預設(shè)的 AsyncTuple 類型,就是 {data: [], loading: boolean, error: boolean} 這樣的數(shù)據(jù)結(jié)構(gòu);
- 使用 AsyncTuple.handleAll 處理 LOADING/SUCCESS/ERROR 這 3 種 action,handleAll 的代碼很簡單,使用 if 判斷 action.type 的后綴即可,源碼在這里。
曾經(jīng) React 和 Angular 是兩個很難調(diào)和的框架,開發(fā)中浪費了我們大量的人力。通過使用輕量級的 iron-redux,完全遵循 Redux 核心原則下,我們內(nèi)部實現(xiàn)了除組件層以外幾乎所有代碼的復用。開發(fā)規(guī)范、工具庫達成一致,開發(fā)人員能夠無縫切換,框架差異帶來的額外成本降到很低。
二、全面擁抱 TypeScript
TypeScript 目前可謂大紅大紫,根據(jù) 2018 stateofjs,超過 50% 的使用率以及 90% 的滿意度,甚至連 Facebook 的 Jest 也正在從 Flow 切換到 TS。如果你還沒有使用,可以考慮切換,絕對能給項目帶來很大提升。過去一年,我們從部分使用 TS 變?yōu)槿媲袚Q到 TS,包括我們自己開發(fā)的工具庫等。
TS 最大的優(yōu)勢是它提供了強大的靜態(tài)分析能力,結(jié)合 TSLint 能對代碼做到更加嚴格的檢查約束。傳統(tǒng)的 EcmaScript 由于沒有靜態(tài)類型,即使有了 ESLint 也只能做到很基本的檢查,一些 typo 問題可能線上出了 Bug 后才被發(fā)現(xiàn)。
下圖是一個前端應用常見的4層架構(gòu)。代碼和工具全面擁抱 TS 后,實現(xiàn)了從后端 API 接口到 View 組件的全鏈路靜態(tài)分析,具有了完善的代碼提示和校驗能力。

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

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

最終 TS 讓代碼更加健壯,尤其是對于大型項目,編譯通過幾乎就代表運行正常,也給重構(gòu)增加了很多信心。
三、回歸 Sass/Less
2015 年我們就開始實踐 CSS Modules,包括后來的 styled-components 等,到 2019 年 css-in-js 方案依舊爭論不休,雖然它確實解決了一些 CSS 語言天生的問題,但同時增加了不少成本,新手不夠友好、全局樣式覆蓋成本高漲、偽類處理復雜、與antd等組件庫結(jié)合有坑。與此同時 Sass/Less 社區(qū)也在飛速發(fā)展,尤其是 Stylelint 的成熟,可以通過技術(shù)約束的手段來避免 CSS 的 Bad Parts。
全局污染:約定每個樣式文件只能有一個頂級類,如 .home-page{ .top-nav {/**/}, .main-content{ /**/ } }。如果有多個頂級類,可以使用 Stylelint rule 檢測并給出警告。
依賴管理不徹底。借助 webpack 的 css-loader,已夠用。
JS 和 CSS 變量共享。關(guān)于 JS 和 Sass/Less 變量共享,我們摸索出了自己的解法:
- // src/styles/variables.jsmodule.exports = {
- // 主顏色
- 'primary-color': '#0C4CFF',
- // 出錯顏色
- 'error-color': '#F15533',
- // 成功顏色
- 'success-color': '#35B34A',};// webpack.config.jsconst styleVariables = 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;}
四、開發(fā)工具覆蓋全鏈路
2019 年,你幾乎不可能再開發(fā)出 React/Angular/Vue 級別的框架,也沒必要再造 Ant-Design/Ng-Zorro 這樣的輪子。難道就沒有機會了嗎?
當然有,結(jié)合你自身的產(chǎn)品開發(fā)流程,依舊有很多機會。下面是常規(guī)項目的開發(fā)流程圖,任何一個環(huán)節(jié)只要深挖,都有提升空間。如果你能通過工具減少一個或多個環(huán)節(jié),帶來的價值更大。

單拿其中的【開發(fā)】環(huán)節(jié)展開,就有很多可擴展的場景:

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

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