React+Redux工程目录结构,最佳实践

参考

  1. Redux进阶系列1: React+Redux项目结构最佳实践
  2. 《深入浅出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 示例采用了这种项目结构划分,如果项目功能比较简单,也还是可以采用这种方式的。

└─src
    │  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' 引入,避免了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工程目录结构如下:

└─src
    │  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

猜你喜欢

转载自blog.csdn.net/runAndRun/article/details/84777508