模块化应用的要点
- 代码文件的组织结构
- 确定模块的边界
- Store 的状态树设计
- 开发辅助工具
代码文件的组织方式
按角色组织
reducer 目录包含所有 Redux 的 reducer
actions 目录包含所有 action 构造函数
components 目录包含所有的傻瓜组件
containers 目录包含所有的容器组件
缺点:不利于应用的扩展,当项目过大对某个功能进行修改时,当修改 containers 目录下某个容器组件时,还需要修改 reducer 和 actions 下对应的文件。
按功能组织
Redux 应用适合“按功能组织”,也就是把完成同一应用功能的代码放在一个目录下,一个应用功能包含多个角色的代码。
在 Redux 中不同角色就是 reducer、actions 和视图,而应用功能对应的就是用户界面上的交互模块。
每个基本应用功能对应的就是一个功能模块,每个功能模块对应一个目录,上图中对应的分别是 todoList 和 filter,每个目录下包含同样名字的角色文件:
- actionTypes.js 定义 action 类型;
- actions.js 定义 action 构造函数,决定了这个功能模块可以接受的动作;
- reducer.js 定义这个功能模块如何相应 actions. 中定义的动作;
- views 目录,包含这个功能模块中所有的 React 组件,包括傻瓜组件和容器组件;
- index.js 这个文件把所有的角色导入,然后统一导出 。
相对于按角色组织的优点:当需要修改某个模块的功能代码时,只用关注对应的目录,所有需要修改的代码都可以在折耳根目录下找到。
确定模块的边界-确定模块的边界
在最理想的情况下,我们应该通过增加代码就能增加系统的功能,而不是通过对现有代码的修改来增加功能 。
一一 Robert C. Martin
低耦合性:不同功能模块之间的依赖关系应该简单而清晰
高内聚性:一个模块应该把自己的功能封装得很好,让外界不要太依赖与自己内部的结构,这样不会因为内部的变化而影响外部模块的功能。
对于整个 React 应用而言,整体有模块构成,但是模块不再是 React 组件,而是由React 组件加上相关 reducer 和 actions 构成的一个小整体。
一个目录可以看作是一个模块,如上图就有两个模块,分别是 todoList 和 filter ,应该明确模块对外的接口,这个接口应该实现把内部封装起来,而在这两个模块下的index.js就是这个模块想要公开出来的接口。
比如,todoList/index.js 中,代码如下:
import * as actions from './actions';
import reducer from './reducer';
import view from './view/container';
export {actions, reducer, view}
//对应的导入
import {actions, reducer, view as TodoList} from '../todoList';
当我们需要修改模块内部的代码结构,比如修改名称将 view 修改为 components 或者将 container 修改为 TodoListView 名称之后,只需要改动 index.js 内容,这个文件 export 出来的内容不会有任何改变。
状态树的设计
状态树的设计遵顼原则:
- 一个模块控制一个状态节点
在 Redux 应用中,Store 上的每个 state 都只能通过 reducer 来更改,每个模块都有机会导出一个自己的 reducer,这个导出的 reducer 最多只能更改 Redux 的状态树上一个节点下的数据,因为 reducer 之间对状态树上的修改权是互斥的,不可能让两个 reducer 都可以修改同一个状态树上的点(可以读取全部的数据,但是只能修改属于自己的那部分) - 避免冗余设计
如果在 Store 上存储冗余数据,那么维持不同部分数据一致性是一个大问题 - 树形结构扁平化
如果一个树形结构层次很深,意味这树形很复杂,一个复杂的状态树是难以管理的,并且深层次树形状态,状态结构会让代码冗长。