参考
- Redux进阶系列1: React+Redux项目结构最佳实践
- 《深入浅出React和Redux》一书的第四章,P76,【4.2 代码文件的组织方式】。
React+Redux 工程目录结构组织
按角色类型组织
如果你用 MVC 框架开发过应用(无论是前端开发还是后端开发),应该知道 MVC 框架之下,通常有这样一种代码组织方式:
controllers/
todoController.js
filterController.js
models/
todoModel.js
filterModel.js
views/
todo.js
todoItem.js
filter.js
Controller、Model、View 分别代表三种模块角色。这种思想,对应到 Redux 应用,就有这种组织方式:
reducers/
todoReducer.js
filterReducer.js
actions/
todoAction.js
filterActions.js
components/
todoList.js
todoItem.js
filter.js
containers/
todoListContainer.js
todoItemContainer.js
角色如下:
- components 目录包含所有的展示组件。
- containers 目录包含所有的容器组件。
- actions 目录包含所有 action 构造函数。
- reducers 目录包含所有 Redux 的 reducer。
充当component、container、action、reducer等不同角色的文件,分别放在对应的多个文件夹下。
Redux 官网的 todomvc 示例采用了这种项目结构划分,如果项目功能比较简单,也还是可以采用这种方式的。
│ index.js
│
├─actions
│ index.js
│
├─components
│ App.js
│ Footer.js
│ Header.js
│ Link.js
│ MainSection.js
│ TodoItem.js
│ TodoList.js
│ TodoTextInput.js
│
├─constants
│ ActionTypes.js
│ TodoFilters.js
│
├─containers
│ FilterLink.js
│ Header.js
│ MainSection.js
│ VisibleTodoList.js
│
├─reducers
│ index.js
│ todos.js
│ visibilityFilter.js
│
└─selectors
index.js
按角色类型组织的缺点
使用这种结构组织项目,每当增加一个新功能时,需要在containers和components文件夹下增加这个功能需要的组件,还需要在actions和reducers文件夹下,分别添加Redux管理这个功能使用到的action和reducer,如果action type是放在另外一个文件夹的话,还需要在这个文件夹下增加新的action type文件。所以,开发一个功能时,你需要频繁的切换路径,修改不同的文件。当项目逐渐变大时,这种项目结构是非常不方便的。
按照功能组织
在Redux框架下,我们可以严格按照模块化思想来开发我们的应用,以最大化的解耦应用,提高代码重用和可维护性。按照功能组织应用的代码,就是这种思想的应用。
一个功能模块对应一个文件夹,这个功能所用到的component、container、action、reducer等文件,都存放在这个文件夹下。
拿Todo应用来说,两个基本的功能就是TodoList和Filter,所以按功能组织就是这样子:
todoList/
components/
componentA.js
componentB.js
containers.js
actions.js
actionTypes.js
index.js
reducer.js
filter/
components/
componentA.js
componentB.js
container.js
actions.js
actionTypes.js
index.js
reducer.js
参考了《深入浅出React和Redux》一书中给出的示例,同时结合自己的理解,有部分调整。
每个功能模块对应一个目录,分别是todoList和filter,每个目录下包含同样的角色文件:
- components 目录下包含功能模块中所有的展示组件。
- container.js 定义容器组件。
- actionTypes.js 定义action类型。
- actions.js 定义action构造函数。
- reducer.js 定义这个功能模块如果响应actions.js定义的动作。
- index.js 把所有的角色导入,然后统一导出。
这种组织方式下,当你要修改某个模块时,只要关注对应的目录即可,所有需要修改的代码文件都能在这个目录下找到。
需要注意的是,按功能组织下的每个模块,都有一个index.js,明确了模块对外的接口。
按照功能组织也有缺点
这种结构也有一个问题,Redux会将整个应用的状态作为一个store来管理,不同的功能模块之间可以共享store中的部分状态(项目越复杂,这种场景就会越多),于是当你在feature1的container中dispatch一个action,很可能会影响feature2的状态,因为feature1和feature2共享了部分状态,会响应相同的action。这种情况下,不同模块间的功能被耦合到了一起。
混合前面两种方式--Ducks
Ducks: Redux Reducer Bundles,是一种新的Redux项目结构组织方式的提议。它提倡将相关联的reducer、action types和action写到一个文件里。本质上是以应用的状态作为模块的划分依据,而不是以界面功能作为划分模块的依据。这样的一个文件(模块)如下:
// 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
// 这样定义而不是每个action都export,目的是简化代码,import时不需要把所有action都列出来。
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 };
}
}
由于使用Ducks提议的目录结构,action creators和reducer定义在同一个文件中,为了避免了引入额外的对象,import *
的导入方式已经不推荐。为了使得导入更加方便以及代码更加简洁,比较好的办法就是,把action creators和action types定义到一个命名空间中(请看以上代码)。
这样,我们在container中使用actions时,可以通过import { actions } from 'path/to/module.js'
引入,这样定义而不是每个action都export,可以简化import代码,import时不需要把所有action都列出来。。
同样的,我们要在container中使用action types时,可以通过import { types } from 'path/to/module.js'
引入。
整体的目录结构如下:
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
应用
应用Ducks工程目录组织方式的思想,《React进阶之路》一书的示例代码第9章,项目bbs-redux-reselect工程目录结构如下:
│ index.js
│
├─components
│ ├─Header
│ │ index.js
│ │ style.css
│ │
│ ├─Loading
│ │ index.js
│ │ style.css
│ │
│ └─ModalDialog
│ index.js
│ style.css
│
├─containers
│ ├─App
│ │ index.js
│ │
│ ├─Home
│ │ index.js
│ │
│ ├─Login
│ │ index.js
│ │ style.css
│ │
│ ├─Post
│ │ │ index.js
│ │ │ style.css
│ │ │
│ │ └─components
│ │ ├─CommentList
│ │ │ index.js
│ │ │ style.css
│ │ │
│ │ ├─CommentsView
│ │ │ index.js
│ │ │ style.css
│ │ │
│ │ ├─PostEditor
│ │ │ index.js
│ │ │ style.css
│ │ │
│ │ └─PostView
│ │ index.js
│ │ style.css
│ │
│ └─PostList
│ │ index.js
│ │ style.css
│ │
│ └─components
│ ├─PostItem
│ │ index.js
│ │ style.css
│ │
│ └─PostsView
│ index.js
│
├─images
│ like-default.png
│ like.png
│
├─redux
│ │ configureStore.js
│ │
│ └─modules
│ app.js
│ auth.js
│ comments.js
│ index.js
│ posts.js
│ ui.js
│ users.js
│
└─utils
AsyncComponent.js
connectRoute.js
date.js
request.js
SHA1.js
url.js
关于作者
- github: https://github.com/uncleAndyChen
- gitee: https://gitee.com/uncleAndyChen
- 个人博客:https://www.lovesofttech.com
- CSDN博客:https://blog.csdn.net/runAndRun
- 邮箱:[email protected]