原文發表於【抹橋的博客-基於 React 的前端項目開發總結】css
咱們的項目主要選用如下技術開發,再配合一些其它輔助工具。html
react前端
react-routernode
reduxreact
react-reduxwebpack
因爲項目是先後端分離的,因此咱們須要一套完整的開發環境,須要包括如下功能:nginx
數據 Mockweb
Webpack 實施編譯刷新redux
方便先後端聯調後端
基於這些需求,咱們基於 Express, Webpack, Webpack-dev-middleware 搭建了這套完整的開發環境。
能夠看到,瀏覽器全部的請求都被本地的 Node.js 服務攔截。對於靜態資源請求,都委託給 webpack-dev-middleware
來處理,對於接口請求根據不一樣的環境來決定要作的操做。
當 ENV = 'development'
時,也就是開發環境,那麼就直接讀取本地的 mock 數據來渲染頁面。
當 ENV = 'api'
時,也就是咱們認爲的聯調環境,這個時候對於接口請求由 node.js 轉發到須要聯調的真實後端服務地址上,從而避免直接調用所產生的跨域問題。
這樣就能夠直接用本地開發代碼和後端聯調,能大大提升效率,省去了每次須要往服務器上構建部署的步驟。
先後端是分開部署的,全部的靜態資源都放在 CDN (example.cdn.com)上面。
也就是說咱們的頁面在 example.cdn/index.html 這裏,可是請求的接口在 example.163.com/api/xxx,咱們確定不能讓用戶去直接去訪問 example.cdn.com/index.html,這樣不合理,並且由跨域問題存在。
那麼訪問 example.dai.163.com 的時候,怎麼拿到咱們的 HTML 頁面呢?
看下圖:
在客戶端和後臺服務之間架設一臺 Nginx, 咱們訪問的 example.dai.163.com 有兩種請求:
HTML 頁面資源
接口請求
這兩種請求都先通過 nginx,在這裏作判斷,若是是頁面請求那麼由 nginx 轉發到 CDN, 不然交給後端服務來響應接口請求。
拿到頁面之後,其它全部的 css, js 等靜態資源都是直接請求到 CDN ,這裏沒什麼說的。
咱們是藉助 redux 來管理數據流的。
咱們來看這張圖。
首先,經過 middleware
和 reducer
生成 store
, 而後得到項目的初始 state
,經過初始 state
去渲染頁面的初始狀態。
以 Home
頁面爲例,首先 Home
經過 react-redux
提供的 connect
方法拿到初始 state
做爲 Home
的 prop
傳遞給 Home
. 而 Home
由多個不一樣的子組件組成,這些組件的須要數據再由 Home 經過 props 傳遞給本身的子組件。
當 Home
的初始狀態加載完畢後,咱們須要向後端請求去拿去一些用戶數據。這時,咱們分發一個下面這種格式的 action
:
{ types: ['home/start','home/success','home/failure'], payload: { api: ... }, meta: { isApi: true } }
全部的 action
都會按照咱們制定的循序經過一個個 middleware
.
在這裏,咱們的 action
會被 callApiMiddleware
經過 meta
裏面的 isApi
標識命中,並去作相應的事情。
好比在這個中間件裏面,咱們去作了真實的接口請求,在請求成功或失敗的時候分發對應的 action
,以及作一些統一的業務邏輯。好比咱們對後端返回的接口中 code
值有統一的約定,假設 1 爲成功, 2 爲失敗, 3 爲未登陸。那麼咱們就能夠在中間件中去處理這些業務邏輯。
當請求成功,並渲染頁面後,假設用戶點擊了一個按鈕,這個按鈕須要喚起 native
的一些功能,好比說拍照。那麼咱們分發一個喚起拍照功能的 camera/start
的action
:
{ types: ['sdk/start','sdk/success','sdk/failure'], payload: { command: ... }, meta: { isSDK: true } }
一樣的道理,這個 action
會被 EpaySDKMiddleware
所識別並處理,在調起 native 的時候, 爲了保證安全性,咱們須要向後發起一個請求去拿簽名,這個時候就能夠在 EpaySDKMiddleware
裏面分發一個接口請求的 action
,那麼這個 action
一樣須要走一遍全部的 middleware
. 那麼這個接口請求的 action
就會像上面的流程同樣,經過 callApiMiddleware
去處理。
中間件的存在,使整個流程變得很是清晰,接口的請求的中間件就只作接口請求,調用 native 接口的中間件就只作對 native 的調用,當對 native 接口的調用須要作後端接口請求的時候,去分發一個 action
走接口請求的中間件。
每一箇中間件只專一於本身的事情,既方便後續的維護,同時也提供了一個很好的拓展方式。
假設咱們由以下的一個路由配置。
{ component: App, path: '/', onEnter: initLogin(store), indexRoute: { getComponent(nextState, cb) { require.ensure([], require => { cb(null, require('../views/Home').default) }, 'Home') }, onEnter: initHome(store) }, childRoutes: [ createActivateRoute(store), { path: 'test', indexRoute: { getComponent(nextState, cb) { require.ensure([], require => { cb(null, require('../views/Test').default) }, 'Test') } } }, ... ] }
那結合 react-route
咱們來看一個完整的流程。當咱們瀏覽器裏面輸入 example.dai.163.com/index.html/#/ 的時候。
首先,經過最上面 線上環境 一節提到的內容,拿到頁面須要 html,css,js.
而後渲染 Provide
和 Router
組件,分別提供 store
的注入和路由的控制。
此時觸發根路徑的路由匹配,而後加載根組件 APP
, 而後根據路由匹配規則匹配到 IndexRouter
, 加載 Home
組件。
後面的事情就和前面數據流轉一節講的是同樣的了。
在先後端徹底分離的基礎上,藉助一套完善的開發環境,能夠大大提升的咱們的開發效率,下降先後端聯調的成本。
同時藉助於 Redux 思想,實現單向數據流,讓咱們能夠實現一個很是清晰的數據流向。而且,藉助於中間件,咱們能夠更加有效的控制數據流轉的過程。爲後面項目的擴展提供無限的想象空間。