朱乙(滬江前端開發工程師)
本文爲原創翻譯,如需轉載請標明出處,不當之處敬請指出css
咱們最近發佈了 React Boilerplate 3.0,在發佈前幾個月,咱們與數百位開發者進行了溝通,討論了他們是如何構建和規模化WEB應用的。下面將咱們從中學到的東西分享給你們。前端
很早咱們就意識到,咱們不但願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以下:
這種在 action 中拉取數據的方法很棒,但存在兩個問題:很難測試這些函數,以及在 action 中包含數據拉取看起來不太對。
Redux 的一大好處就是 pure action creator 很容易測試。而在 action 中加入了 thunk 的數據拉取操做以後,你必須兩次調用 action,模擬 dispatch 函數等。
最近,redux-saga 在 React 世界引發了普遍關注。redux-saga 利用了 ES6 的 Generator 函數,讓異步代碼看起來就像同步代碼同樣,並且讓異步流很是容易測試。redux-saga 給人的感受是一個單獨處理全部異步事務的獨立線程,不會干擾應用的其餘方面!
下面是 redux-saga 的一個例子:
上面的代碼讀起來就像小說同樣,避免了回調地獄,並且很是容易測試。爲何?這是由於 redux-saga 導出的這些「做用」(effect)無需完成便可進行測試。
咱們在文件頂部 import 的這些做用都是 handler,可讓咱們輕鬆與 redux 交互:
put() 派發一個action
take() 暫停 saga 直到action發生
select() 獲取部分 redux 狀態(有點像 mapStateToProps)
call() 調用傳入的第一個位置的函數,並將其餘參數做爲被調用函數的參數
爲何這些做用有用?讓咱們看看下面的測試:
只有 generator.next() 被調用時,generator 函數纔會繼續,直到遇到下一個 yield 關鍵字!另外,咱們也把測試文件和組件放在了一塊兒。
用 redux-saga 來作膠水中間件
咱們的組件如今真正解耦了。它們不關心其餘組件的樣式或邏輯;它們只關心本身的事情(絕大多數狀況下如此)。
假設有一個 Clock 和一個 Timer 組件。當 Clock 中的按被按下時,咱們想要啓動 Timer;當 Timer 中的中止按鈕被按下時,咱們想要在 Clock 中顯示時間。
你也許會這麼作:
但這麼作的話,你沒法單獨使用這兩個組件,複用它們幾乎不可能!
不過,咱們能夠用 redux-saga 來作這兩個解耦組件的「膠水中間件」。取決於應用類型,咱們能夠經過聽取特定 action,來以不一樣方式做出反應,從而讓組件真正變得可複用。
先修改兩個組件:
注意兩個組件如今只關心本身,只 import 了本身的 action !
下面用一個 saga 來把這兩個解耦的組件鏈接到一塊兒:
漂亮!
容器和組件的區別
按功能組織代碼文件
使用 CSS modules 和 PostCSS Auto Reset
使用 redux-saga:
得到可讀而且可測試的異步流
將解耦組件鏈接到一塊兒
iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。