组织项目结构
关于如何组织 React+Redux 的项目结构一直有多种声音,目前主流的方案有三种:按照类型、按照页面功能和 Ducks。
按照类型
这里的类型指的是一个文件在项目中充当的角色类型,即这个文件是一个 component(展示组件)还是一个 container(容器组件),或者是一个 reducer 等,充当 component、container、action、reducer 等不同角色的文件分别放在不同的文件夹下,这也是 Redux 官方网站示例所采用的项目结构。这种结构如下:

使用这种结构组织项目,每当增加一个新功能时,需要在 containers 和 components 文件夹下增加这个功能需要的组件,还要在 actions 和 reducers 文件夹下分别添加 Redux 管理这个功能使用到的 action 和 reducer,如果 action type 放在另一个文件夹,还需要在这个文件夹下增加新的 action type。所以,开发一个功能时,需要频繁地切换路径以修改不同的文件。如果项目比较小,例如第 8 章中的 todos 项目采用这种结构问题还不大,但对于一个规模较大的项目来说,使用这种项目结构是非常不方便的。
按照页面功能
一个页面功能对应一个文件夹,这个页面功能所用到的 container、component、action、reducer 等文件都存放在这个文件夹下,如下所示:

这种项目结构的好处显而易见,一个页面功能使用到的组件、状态和行为都在同一个文件夹下,方便开发,易于功能的扩展,Github 上很多脚手架也选择了这种目录结构,如 https://github.com/react-boilerplate/react-boilerplate 。但使用这种结构依然无法解决开发一个功能时,需要频繁在 reducer、action、action type 等不同文件间切换的问题。另外,Redux 将整个应用的状态放在一个 store 中来管理,不同的功能模块之间可以共享 store 中的部分状态(项目越复杂,这种场景就会越多),共享的状态应该放到哪一个页面功能文件夹下也是一个问题。这些问题归根结底是因为 Redux 中的状态管理逻辑并不是根据页面功能划分的,它是页面功能之上的一层抽象。
Ducks
Ducks 指的是一种新的 Redux 项目结构的提议,这份提议的地址是: https://github.com/erikras/ducks-modular-redux 。它提倡将相关联的reducer、action types和action creators 写到一个文件里。本质上是以应用的状态作为划分模块的依据,而不是以界面功能作为划分模块的依据。这样,管理相同状态的依赖都在同一个文件中,无论哪个容器组件需要使用这部分状态,只需要引入管理这个状态的模块文件即可。这样的一个文件(模块)代码如下:

整体的目录结构如下:

在前两种项目结构中,当 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 定义到一个命名空间中:

这样,在 container 中使用 action creators 时,可以通过 import { actions } from 'path/to/module.js' 引入,避免引入额外的对象,也避免逐个导入 action creator 的烦琐。
采用 Ducks 这种项目结构重新组织BBS项目的目录结构,最终的目录结构如图9-1所示。
这里,我们把 action types、action creators 和 reducer 组成的每一个模块都放到了 redux/modules 路径下,而不是直接放在 redux 文件夹下,一方面是因为 redux 文件夹下可能还需要放置其他与 redux 相关的模块,例如自定义的 Middleware;另一方面,增加一层 modules 文件夹更能体现其作为核心业务逻辑模块的意义。模块的划分方式及其具体内容接下来就会介绍。另外,components 文件夹下的很多页面专用组件移动到对应页面下的 components 文件夹中,例如,PostItem 移动到 PostList/components 下,PostView、PostEditor 和 CommentList 移动到 Post/components 下,如图9-2所示。src/components 中只保留具有全局通用性质的组件,例如页面加载效果组件 Loading、用于显示错误信息的模态框组件 ModalDialog 等。

