原文地址在个人博客, 转载请注明出处,谢谢!javascript
本文是《使用React技术栈的一些收获》系列文章的第一篇(第二篇在这里,介绍了React的一些原理)。这篇文章则介绍了大型React项目是如何架构的以及架构的原理和思想。项目背景是一个博客发布平台,相似于简书,项目地址时光笔记(还未完善...)java
项目技术栈使用的是React全家桶:React+redux+react router+es6+webpack+sass
以及Data到View层咱们使用了reselect
。因为数据处理逻辑并不复杂,所以并无使用immutable.js
和Redux saga
(后来我以为连Redux
都不必用);样式方面考虑到可读性和开发人数较少(俩),咱们并无使用流行的CSS-module。react
选择脚手架就选择了总体架构,我选择的是davezuko大神的react-redux-starter-kit,也是最受欢迎的脚手架之一。并在它的基础上安装了一些用到的包,删去了一些不用的包,让它更适合咱们的项目。webpack
项目目录以下:
git
根据脚手架的架构,咱们构建的是一个React单页应用。es6
就是采用React router plain object
+combineReducer
+require.ensure
的写法把不一样的路由分割在routes目录下,对应不一样的页面,作代码分割、按需加载。逻辑图以下:github
首先src目录下有一个main.js
,它用来建立store
,并拿到路由(plain object
形式),而后注入到顶层的Provider
组件和其下的Router
组件:web
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 ) }
redux
的store
也随着页面分割而分割:
redux
不一样页面下的modules下的文件只负责本页面所需的全部action
和reducer
,并经过加载页面inject
主reducer
里,而后在src/store/reduce.js文件里combine
,最后被引入到src/store/createStore里和同时引入的redux中间件一块儿建立store
:sass
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))//注入时更新 }
const store = createStore( makeRootReducer(), initialState, compose( applyMiddleware(...middleware), ...enhancers ) )
routes
目录下有一个index.js
文件,它使用plain object
的写法集合各路由对应的页面;
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
按需加载页面的container
和reducer
:
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。使用这种方式的话,目录结构将会变得更简单、更容易理解,避免了多层嵌套,所以,项目还须要改善。