React + Redux + react router技術棧架構

React + Redux + react router技術棧架構

來自專欄 莫凡的前端專欄

原文地址在我的博客, 轉載請註明出處,謝謝!

前言

前些日子剛學習了React,覺得很不錯,很符合我的邏輯。於是還沒弄明白,就迫不及待的開始了一個中型項目(我覺得)(其實是項目需要趕緊開始,沒時間了...咳咳)。期間不出所料地遇到了很多坑和問題,也得到了很多收穫,特開幾篇文章記錄下來。

概述

本文是《使用React技術棧的一些收穫》系列文章的第一篇(第二篇在這裡,介紹了React的一些原理)。這篇文章則介紹了大型React項目是如何架構的以及架構的原理和思想。項目背景是一個博客發布平台,類似於簡書,項目地址時光筆記(還未完善...)

具體技術棧

項目技術棧使用的是React全家桶:React+redux+react router+es6+webpack+sass以及Data到View層我們使用了reselect。由於數據處理邏輯並不複雜,因此並沒有使用immutable.js和Redux saga(後來我覺得連Redux都沒必要用);樣式方面考慮到可讀性和開發人數較少(倆),我們並沒有使用流行的CSS-module。

腳手架的選擇

選擇腳手架就選擇了整體架構,我選擇的是davezuko大神的react-redux-starter-kit,也是最受歡迎的腳手架之一。並在它的基礎上安裝了一些用到的包,刪去了一些不用的包,讓它更適合我們的項目。

項目架構

項目目錄如下:

根據腳手架的架構,我們構建的是一個React單頁應用。

總體來說

就是採用React router plain object+combineReducer+require.ensure的寫法把不同的路由分割在routes目錄下,對應不同的頁面,做代碼分割、按需載入。邏輯圖如下:

具體來說

首先src目錄下有一個main.js,它用來創建store,並拿到路由(plain object形式),然後注入到頂層的Provider組件和其下的Router組件:

src下的main.js文件:

const initialState = window.___INITIAL_STATE__const store = createStore(initialState)// 創建storeconst MOUNT_NODE = document.getElementById(root)let render = () => { const routes = require(./routes/index).default(store)// 拿到路由 ReactDOM.render( <AppContainer store={store} routes={routes} />, //注入 MOUNT_NODE )}

redux的store也隨著頁面分割而分割:

不同頁面下的modules下的文件只負責本頁面所需的所有action和reducer,並通過載入頁面inject主reducer里,然後在src/store/reduce.js文件里combine,最後被引入到src/store/createStore里和同時引入的redux中間件一起創建store:

src/store目錄下的reducer.js:

export const makeRootReducer = (asyncReducers) => { return combineReducers({ auth: auth, form: formReducer, location: locationReducer, ...asyncReducers // 各頁面下的reducer注入到這裡 })}export const injectReducer = (store, { key, reducer }) => { store.asyncReducers[key] = reducer store.replaceReducer(makeRootReducer(store.asyncReducers))//注入時更新}

以及src/store下的createStore文件:

const store = createStore( makeRootReducer(), initialState, compose( applyMiddleware(...middleware), ...enhancers ) )

routes目錄下有一個index.js文件,它使用plain object的寫法集合各路由對應的頁面;

routes下的index.js文件:(用來包含各頁面)

src/routes/index.js:(採用React router plain object寫法)

import CoreLayout from ../layouts/CoreLayoutimport Home from ./Homeimport FollowRoute from ./Followimport SignRoute from ./Signimport HallRoute from ./Hallimport UserPageRoute from ./UserPageimport PageNotFound from ./PageNotFoundimport Redirect from ./PageNotFound/redirectexport const createRoutes = (store) => ({ path: /, component: CoreLayout, indexRoute: Home, childRoutes: [ // 各頁面 FollowRoute(store), SignRoute(store), HallRoute(store), UserPageRoute(store), PageNotFound(), Redirect ]})

每個頁面目錄下也有一個index.js文件並使用getComponent + webpack ensure按需載入頁面的container和reducer:

每個頁面下的index.js文件:(負責輸出這個頁面)

src/routes/sign/index.js(其他頁面差不多,舉個例子)

import { injectReducer } from ../../store/reducers// 引入注入reducer函數export default (store) => ({ path: sign, //頁面路由 getComponent (nextState, cb) { require.ensure([], (require) => { // webpack按需載入 const Sign = require(./containers/SignContainer).default //引入總container const reducer = require(./modules/index).default//引入總reducer injectReducer(store, { key: sign, reducer })// 載入時注入頁面reducer到主reducer cb(null, Sign)// 返回頁面 }) }})

在每個頁面下,index.js是獲得每個頁面的入口,每個頁面都有自己的components和containers以及actions和reducers,目錄看起來像這樣:

components和containers都是這個頁面下的組件和容器,如果其他頁面也會使用裡面的組件和容器,就會把他們放在src/component和src/containers下共用。modules下的文件是這個頁面所有的action和reducer。如果頁面邏輯可以分離,會把各邏輯下的reducer抽離並單開一個index.js,並在其中combine:

=>

總結與反思

通過上述架構,項目代碼邏輯變得很清晰,每一個文件都有其專屬的功能,互不影響,開發過程變得工程化、流程化,思路很清晰,代碼出錯率大大降低,開發速度大大提高。React router plain object+redux combineReducer的組合很好的將代碼按不同頁面做了分割;而 getComponent + webpack ensure又做到了頁面的按需載入,項目頁面運行速度提升了不少。但是有一個問題,在react route4.0版本中getComponent被移除了,並提供了更加簡潔的方式(實際上就是替你做了按需載入):Bundle組件+webpack 載入器undle-loader。使用這種方式的話,目錄結構將會變得更簡單、更容易理解,避免了多層嵌套,因此,項目還需要改善。


推薦閱讀:

TAG:React | Redux | reactrouterredux |