翻譯 | 如何規模化 React 應用

朱乙(滬江前端開發工程師)
本文爲原創翻譯,如需轉載請標明出處,不當之處敬請指出css

咱們最近發佈了 React Boilerplate 3.0,在發佈前幾個月,咱們與數百位開發者進行了溝通,討論了他們是如何構建和規模化WEB應用的。下面將咱們從中學到的東西分享給你們。前端

clipboard.png

很早咱們就意識到,咱們不但願React Boilerplate僅僅是「另外一個模板項目」,咱們但願它能爲開發者提供創業或研發產品的最優解決方案。react

過去,規模化大多和服務器系統有關。隨着使用的用戶愈來愈多,你須要確保能在集羣中添加更多的服務器,數據庫可以拆分到多臺服務器上。數據庫

但如今,隨着富WEB應用的崛起,規模化在前端也變得愈來愈重要!一個複雜應用的前端也須要能應付得了大量的用戶、開發者和模塊,須要重視這三個方面(用戶、開發者和組件)的規模化,不然後面就會碰到問題。redux

容器和組件

對於一個大的應用,第一個大的改進就是,瞭解有狀態(容器)和無狀態(組件)組件的區別。容器管理數據和鏈接狀態,一般沒有樣式。而組件則有樣式,但不負責任何數據和狀態的管理,最初我比較迷惑。基本上,容器負責讓組件正常工做,組件負責東西如何展現。服務器

根據這一點,咱們清楚地區分了可複用組件和數據管理層。如今你去編輯組件時,就不用擔憂會搞混數據結構;編輯容器,也不用擔憂弄亂樣式了。按照這個思路開發應用將會變得很簡單!數據結構

代碼結構

按照慣例,開發者們會按照類型來組織結構,好比使用 actions/,components/,containers/ 等文件夾。架構

假設有一個名稱爲NavBar的導航條容器,容器會管理與之相關的狀態,還有一個toggleNav動做來打開關閉它。下面就是按類型組織的文件結構:app

react-app-by-type
├── css
├── actions
│ └── NavBarActions.js
├── containers
│ └── NavBar.jsx
├── constants
│ └── NavBarConstants.js
├── components
│ └── App.jsx
└── reducers
└── NavBarReducer.js

這種組織方式用來作樣例還能夠,但若是你有成百上千個組件,開發就會很困難。每添加一項功能,你就必須在上千個文件中找到正確的文件。這很是討厭,也容易讓人感到疲勞。異步

通過長期追蹤咱們的 Github 問題列表和不少不一樣嘗試後,咱們找到了一種更好的解決方案:

那就是按照功能來組織代碼!也就是,把和某個功能(好比導航條)相關的全部文件放到同一個文件夾裏。

下面是從新組織的 NavBar 代碼:

react-app-by-feature
├── css
├── containers
│    └── NavBar
│       
├── NavBar.jsx
│        ├── actions.js
│        ├── constants.js
│        └── reducer.js
└── components
└── App.jsx

開發這個應用,只須要進入一個目錄,若是添加一個功能,也只須要建立一個目錄。這種方式很是有利於文件的重命名、文件和替換,多人協做也不會致使任何衝突。

當我第一次知道這種開發React應用的方式時,我在想,我爲何還要按照原來的思路作?這種開發方式太讚了!

須要注意的是,這種組織方式並不意味着 redux actions 和 reducers 只能用在這個組件裏,它們能夠(也應該)用於其餘組件中!

當我按照這個思路開發的時候想到了兩個問題:

1.該如何處理樣式?
2.該如何處理數據獲取?

樣式管理

除告終構的考慮以外,因爲CSS自己的兩個特色,在基於組件的架構中使用 CSS 很難:全局命名和繼承。

惟一的類名

假設在某個大型應用中存在這段 CSS代碼:

.header { /* … */ }
.title {
       background-color: yellow;
}

很快你就會發現問題,title 太經常使用了。其餘開發者也可能會寫出如下代碼:

.footer { /* … */ }
.title {
       border-color: blue;
}

這就產生了一個命名衝突,而後你的title樣式就多了一個藍色邊框,讓你不得不在上千個文件中找到這段代碼。

幸運的是,有大牛爲咱們找到了解決方案,那就是 CSS Modules。CSS Modules 的關鍵之處在於:把組件的樣式放在組件本身的文件夾裏。

react-app-with-css-modules
├── containers
└── components     
   └── Button         
      ├── Button.jsx         
      └── styles.css

如今的CSS看起來沒有任何區別,不過咱們再也不擔憂命名衝突了,可使用很是常見的名字:

.button {
   /* … */
}

而後就能夠在組件中經過require或import引入這些CSS文件,並在 className 中使用了:

/* Button.jsx */
var styles = require('./styles.css');
<div className={styles.button}></div>

若是你如今去查看對應的 DOM 結構,你會看到<div class="MyApp__button__1co1k"></div>!CSS Modules 幫你保證了類名的「惟一」。

爲每一個組件重置屬性

在 CSS 中,特定的屬性會在 DOM 節點上下繼承。好比,若是父節點有 line-height,而子節點沒有,子節點就會自動繼承父節點的 line-height。

在基於組件的架構中,這可不是咱們想要的。好比下面的代碼:

.header {
   line-height: 1.5em;
   /* … */
}

.footer {
   line-height: 1;
   /* … */
}

假設咱們在這兩個組件中渲染一個 Button,這個 Button 在這兩個組件裏會有不一樣的外觀!這一點不只適用於 line-height,也適用於其餘能繼承的屬性。

在之前,咱們能夠用 Reset CSS、Normalize.css 和 sanitize.css 來重置樣式表。但若是咱們想將這一理念落實到每個組件上呢?

這就是PostCSS 插件PostCSS Auto Reset 的用途!它會對每一個組件進行樣式重置,將全部能繼承的 CSS 屬性設置成默認值,從而覆蓋繼承。

數據拉取

基於組件的架構面臨的第二大問題就是數據拉取。對於大部分 action 來講,將 action 和組件放在一塊兒很合理,但數據拉取是全局 action,不和某個組件相連!

目前大多數開發者使用 Redux Thunk來處理Redux 中的數據拉取。標準的thunked action以下:

clipboard.png

這種在 action 中拉取數據的方法很棒,但存在兩個問題:很難測試這些函數,以及在 action 中包含數據拉取看起來不太對。

Redux 的一大好處就是 pure action creator 很容易測試。而在 action 中加入了 thunk 的數據拉取操做以後,你必須兩次調用 action,模擬 dispatch 函數等。

最近,redux-saga 在 React 世界引發了普遍關注。redux-saga 利用了 ES6 的 Generator 函數,讓異步代碼看起來就像同步代碼同樣,並且讓異步流很是容易測試。redux-saga 給人的感受是一個單獨處理全部異步事務的獨立線程,不會干擾應用的其餘方面!

下面是 redux-saga 的一個例子:

clipboard.png

上面的代碼讀起來就像小說同樣,避免了回調地獄,並且很是容易測試。爲何?這是由於 redux-saga 導出的這些「做用」(effect)無需完成便可進行測試。

咱們在文件頂部 import 的這些做用都是 handler,可讓咱們輕鬆與 redux 交互:

  • put() 派發一個action

  • take() 暫停 saga 直到action發生

  • select() 獲取部分 redux 狀態(有點像 mapStateToProps)

  • call() 調用傳入的第一個位置的函數,並將其餘參數做爲被調用函數的參數

爲何這些做用有用?讓咱們看看下面的測試:

clipboard.png

只有 generator.next() 被調用時,generator 函數纔會繼續,直到遇到下一個 yield 關鍵字!另外,咱們也把測試文件和組件放在了一塊兒。

用 redux-saga 來作膠水中間件

咱們的組件如今真正解耦了。它們不關心其餘組件的樣式或邏輯;它們只關心本身的事情(絕大多數狀況下如此)。

假設有一個 Clock 和一個 Timer 組件。當 Clock 中的按被按下時,咱們想要啓動 Timer;當 Timer 中的中止按鈕被按下時,咱們想要在 Clock 中顯示時間。
你也許會這麼作:

clipboard.png

但這麼作的話,你沒法單獨使用這兩個組件,複用它們幾乎不可能!

不過,咱們能夠用 redux-saga 來作這兩個解耦組件的「膠水中間件」。取決於應用類型,咱們能夠經過聽取特定 action,來以不一樣方式做出反應,從而讓組件真正變得可複用。

先修改兩個組件:

clipboard.png

注意兩個組件如今只關心本身,只 import 了本身的 action !

下面用一個 saga 來把這兩個解耦的組件鏈接到一塊兒:

clipboard.png

漂亮!

總結

  • 容器和組件的區別

  • 按功能組織代碼文件

  • 使用 CSS modules 和 PostCSS Auto Reset

  • 使用 redux-saga:

    • 得到可讀而且可測試的異步流

    • 將解耦組件鏈接到一塊兒

iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。

相關文章
相關標籤/搜索