React + Redux + react router技术栈架构

原文地址在我的博客, 转载请注明出处,谢谢!

概述

本文是《使用React技术栈的一些收获》系列文章的第一篇(第二篇在这里,介绍了React的一些原理)。这篇文章则介绍了大型React项目是如何架构的以及架构的原理和思想。项目背景是一个博客发布平台,类似于简书,项目地址时光笔记(还未完善...)

具体技术栈

项目技术栈使用的是React全家桶:React+redux+react router+es6+webpack+sass以及Data到View层我们使用了reselect。由于数据处理逻辑并不复杂,因此并没有使用immutable.jsRedux saga(后来我觉得连Redux都没必要用);样式方面考虑到可读性和开发人数较少(俩),我们并没有使用流行的CSS-module。

脚手架的选择

选择脚手架就选择了整体架构,我选择的是davezuko大神的react-redux-starter-kit,也是最受欢迎的脚手架之一。并在它的基础上安装了一些用到的包,删去了一些不用的包,让它更适合我们的项目。

项目架构

项目目录如下:

项目主目录
src目录
routes目录

根据脚手架的架构,我们构建的是一个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)// 创建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目录

不同页面下的modules下的文件只负责本页面所需的所有actionreducer,并通过加载页面injectreducer里,然后在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/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。使用这种方式的话,目录结构将会变得更简单、更容易理解,避免了多层嵌套,因此,项目还需要改善。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容