React + Redux + react router技術棧架構

原文地址在個人博客, 轉載請註明出處,謝謝!javascript

概述

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

具體技術棧

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

腳手架的選擇

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

項目架構

項目目錄以下:
項目主目錄src目錄routes目錄git

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

整體來講

就是採用React router plain object+combineReducer+require.ensure的寫法把不一樣的路由分割在routes目錄下,對應不一樣的頁面,作代碼分割、按需加載。邏輯圖以下:
項目架構原理圖github

具體來講

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

src下的main.js文件:
const initialState = window.___INITIAL_STATE__
const store = createStore(initialState)// 建立store

const MOUNT_NODE = document.getElementById('root')

let render = () => {
  const routes = require('./routes/index').default(store)// 拿到路由

  ReactDOM.render(
    <AppContainer store={store} routes={routes} />, //注入
    MOUNT_NODE
  )
}

reduxstore也隨着頁面分割而分割:
routes目錄redux

不一樣頁面下的modules下的文件只負責本頁面所需的全部actionreducer,並經過加載頁面injectreducer裏,而後在src/store/reduce.js文件裏combine,最後被引入到src/store/createStore裏和同時引入的redux中間件一塊兒建立storesass

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/CoreLayout'
import Home from './Home'
import FollowRoute from './Follow'
import SignRoute from './Sign'
import HallRoute from './Hall'
import UserPageRoute from './UserPage'
import PageNotFound from './PageNotFound'
import Redirect from './PageNotFound/redirect'

export const createRoutes = (store) => ({
  path: '/',
  component: CoreLayout,
  indexRoute: Home,
  childRoutes: [        // 各頁面
    FollowRoute(store),
    SignRoute(store),
    HallRoute(store),
    UserPageRoute(store),
    PageNotFound(),
    Redirect
  ]
})

每一個頁面目錄下也有一個index.js文件並使用getComponent + webpack ensure按需加載頁面的containerreducer

每一個頁面下的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是得到每一個頁面的入口,每一個頁面都有本身的componentscontainers以及actionsreducers,目錄看起來像這樣:
Alt 每一個頁面下的目錄結構

componentscontainers都是這個頁面下的組件和容器,若是其餘頁面也會使用裏面的組件和容器,就會把他們放在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。使用這種方式的話,目錄結構將會變得更簡單、更容易理解,避免了多層嵌套,所以,項目還須要改善。

相關文章
相關標籤/搜索