React + Redux 是React生态中使用最频繁的技术栈,但关于如何组织React+Redux的项目结构,一直都有多种声音。本文将讨论其中最经常使用的3种项目结构,并给出我的的最佳实践。前端
1.按照类型react
这里的类型指的是一个文件在项目中充当的角色类型,即这个文件是一个component,仍是一个container,或者是一个reducer等,充当component、container、action、reducer等不一样角色的文件,分别放在不一样的文件夹下,这也是Redux官网示例所采用的项目结构。这种结构以下所示:git
actions/ a.js b.js components/ a1.js a2.js b1.js constainers/ a.js b.js reducers/ a.js b.js index.js
使用这种结构组织项目,每当增长一个新功能时,须要在containers和components文件夹下增长这个功能须要的组件,还须要在actions和reducers文件夹下,分别添加Redux管理这个功能使用到的action和reducer,若是action type是放在另一个文件夹的话,还须要在这个文件夹下增长新的action type文件。因此,开发一个功能时,你须要频繁的切换路径,修改不一样的文件。当项目逐渐变大时,这种项目结构是很是不方便的。github
2.按照功能redux
一个功能模块对应一个文件夹,这个功能所用到的container、component、action、reducer等文件,都存放在这个文件夹下。以下所示:spa
feature1/ components/ actions.js container.js index.js reducer.js feature2/ components/ actions.js container.js index.js reducer.js index.js rootReducer.js
这种项目结构的好处显而易见,一个功能中使用到的组件、状态和行为都在同一个文件夹下,方便开发,易于功能的扩展,Github上不少脚手架也选择了这种目录结构,如https://github.com/react-boil...。但这种结构也有一个问题,Redux会将整个应用的状态做为一个store来管理,不一样的功能模块之间能够共享store中的部分状态(项目越复杂,这种场景就会越多),因而当你在feature1的container中dispatch一个action,极可能会影响feature2的状态,由于feature1和feature2共享了部分状态,会响应相同的action。这种状况下,不一样模块间的功能被耦合到了一块儿。code
3.Duckscomponent
Ducks实际上是对一种新的Redux项目结构的提议。它提倡将相关联的reducer、action types和action写到一个文件里。本质上是以应用的状态做为模块的划分依据,而不是以界面功能做为划分模块的依据。这样,管理相同状态的依赖都在同一个文件中,无论哪一个容器组件须要使用这部分状态,只须要在这个组件中引入这个状态对应的文件便可。这样的一个文件(模块)以下:对象
// widget.js // Actions const LOAD = 'widget/LOAD'; const CREATE = 'widget/CREATE'; const UPDATE = 'widget/UPDATE'; const REMOVE = 'widget/REMOVE'; const initialState = { widget: null, isLoading: false, } // Reducer export default function reducer(state = initialState, action = {}) { switch (action.type) { LOAD: //... CREATE: //... UPDATE: //... REMOVE: //... default: return state; } } // Action Creators export function loadWidget() { return { type: LOAD }; } export function createWidget(widget) { return { type: CREATE, widget }; } export function updateWidget(widget) { return { type: UPDATE, widget }; } export function removeWidget(widget) { return { type: REMOVE, widget }; }
总体的目录结构以下:接口
components/ (应用级别的通用组件) containers/ feature1/ components/ (功能拆分出的专用组件) feature1.js (容器组件) index.js (feature1对外暴露的接口) redux/ index.js (combineReducers) module1.js (reducer, action types, actions creators) module2.js (reducer, action types, actions creators) index.js
在前两种项目结构中,当container须要使用actions时,能够经过import * as actions from 'path/to/actions.js'
方式,一次性把一个action文件中的全部action creators都引入进来。但在使用Ducks结构时,action creators和reducer定义在同一个文件中,import *
的导入方式会把reducer也导入进来(若是action types也被export,那么还会导入action types)。咱们能够把action creators和action types定义到一个命名空间中,解决这个问题。修改以下:
// widget.js // Actions export const types = { const LOAD : 'widget/LOAD', const CREATE : 'widget/CREATE', const UPDATE : 'widget/UPDATE', const REMOVE : 'widget/REMOVE' } const initialState = { widget: null, isLoading: false, } // Reducer export default function reducer(state = initialState, action = {}) { switch (action.type) { types.LOAD: //... types.CREATE: //... types.UPDATE: //... types.REMOVE: //... default: return state; } } // Action Creators export const actions = { loadWidget: function() { return { type: types.LOAD }; }, createWidget: createWidget(widget) { return { type: types.CREATE, widget }; }, updateWidget: function(widget) { return { type: types.UPDATE, widget }; }, removeWidget: function(widget) { return { type: types.REMOVE, widget }; } }
这样,咱们在container中使用actions时,能够经过import { actions } from 'path/to/module.js'
引入,避免了引入额外的对象,也避免了import时把全部action都列出来的繁琐。
如今的Ducks结构就是我项目中正在使用的项目结构,用起来仍是很顺畅的,欢迎你们提出改进建议!
欢迎关注个人公众号:老干部的大前端,领取21本大前端精选书籍!