深入浅出React+Redux(六:模块化 React 和 Redux 应用)

前言

我们已经介绍了React 的基本工作方式,也知道了 Redux 在组合React 组件中的作用,但是更多的只是了解其基本原理和使用方法 。

但是接下来我们将要了解为以下几个方面

1) 模块化应用的要点
2) 代码文件的组织方式
3) 状态树的设计
4) 开发辅助工具

(一)模块化应用

简单的看,React和Redux都执行一个公式

 UI=render(state)

来产生用户界面,但是架构一个新应用,我们必须考虑以下几点

1) 码文件的组织结构;
2) 确定模块的边界;
3) Store 的状态树设计。

最后我们会动手写一个TODO应用,来学习各个层次的知识。

(二)代码文件的组织方式

(1)接角色组织

目前包含Redux的github和网上大多数教程都采用这种方式。

深入浅出React+Redux(六:模块化 React 和 Redux 应用)_第1张图片

reducer 目录包含所有 Redux 的 reducer;
actions 目录包含所有 action 构造函数;
components 目录包含所有的傻瓜组件;
views目录包含所有的容器组件 。

当然我们上家也是这样基于角色组织,但是项目大后。就发现一个问题,当我们修改views内部容器组件,就需要同时修改actions和actionTypes和 Reducers 内相对应的文件。这无疑让人头疼。

(2)接功能组织

Redux 应用适合于“按功能组织”( Organzied by Feature ),也就是把完成同一应用功能的代码放在一个目录下,一个应用功能包含多个角色的代码 。
在 Redux 中,不同的角色就是 reducer 、 actions 和视图 而应用功能对应的就是用户界面上的交互模块。

我们以 Todo 应用为例子,这个应用的两个基本功能就是 TodoList 和 Filter ,所以代码就这样组织,文件目录列表如下:

深入浅出React+Redux(六:模块化 React 和 Redux 应用)_第2张图片

这里,我将原来角色布局顶层的views更改为pages。Todo内部有两个功能模块。
每个基本功能对应的其实就是一个功能模块,每个功能模块对应一个目录,这个例子中分别是 todoList 和 filter ,每个目录下包含同样名字的角色文件:

actionTypes.js 定义 action 类型;
actions.js 定义 action 构造函数,决定了这个功能模块可以接受的动作;
reducer.js 定义这个功能模块如何相应 actions. 中定义的动作;
views 目录,包含这个功能模块中所有的 React 组件,包括傻瓜组件和容器组件;
index.js 这个文件把所有的角色导入,然后统一导出 。

在这种组织方式下,当你要修改某个功能模块的代码的时候,只要关注对应的目录就行了,所有需要修改的代码文件都在能这个目录下找到 。


(三)模块接口

在最理想的情况下,我们应该通过增加代码就能增加系统的功能,而不是通过对现有代码的修改来增加功能 。
一一-Robert C. Martin

不同功能模块之间的依赖关系应该简单而且清晰,也就是所谓的保持模块之间低耦合性;
一个模块应该把自己的功能封装得很好,让外界不要太依赖与自己内部的结构,这样不会因为内部的变化而影响外部模块的功能,这就是所谓高内聚性。

整个 Redux 应用而言,整体由模块构成,但是模块不再是 React 组件,而是由 React组件加上相关 reducer 和 actions 构成的一个小整体 。

以我们将要实现实现的 Todo 应用为例,功能模块就是 todoList 和 filter,这两个功能模块分别用各自的 React 组件、 reducer 和 action 定义 。然后,通过 index.js 文件,这个文件就是
我们的模块接口 。

(四)状态树的设计

状态树设计要遵循如下几个原则:

一个模块控制一个状态节点
避免冗余数据
树形结构扁平
  • 一个模块控制一个状态节点

Redux Store上的全部状态,在任何时候,对任何模块都是开放的,通过 store.getState()总能够读取当整个状态树的数据,但是只能更新自己相关那一部分模块的数据 。

  • 避免冗余数据

冗余数据是一致性的大敌,如果在 Store 上存储冗余数据,那么维持不同部分数据一致就是一个大问题

  • 树形结构扁平

如果树形结构层次很深,往往意味着树形很复杂,一个很复杂的状态树是难以管理


(四)Todo 应用实例

相关代码已上传github。

todo-feature分支

首先, 从上面的项目结构可以看出,我们将Todo页面分为Filter,和TodoList模块。那么Todo应用Store的设计如下

深入浅出React+Redux(六:模块化 React 和 Redux 应用)_第3张图片

上面图,todos是个list数组,当每新增一个元素,都要标记当前事项completed字符为‘已完成’或者‘未完成’。而那些事项显示,则有todos和filter共同决定。


顶层TodoList/index.js 负责组装

import React from 'react';
import {view as Todos} from './TodoList';
import {view as Filter} from './Filter';

function Todo() {
  return (
      
); } export default Todo;

在views/addTodo.js 中

onSubmit(ev) {
    ev.preventDefault();

    const input = this.input;
    if (!input.value.trim()) {
      return;
    }

    this.props.onAdd(input.value);
    input.value = '';
  }

  refInput(node) {
    this.input = node;
  }

  render() {
    return (
      
"add-todo">
this.onSubmit}> "new-todo" ref={this.refInput} />
) }

当一个包含 ref属性的组件完成装载( mount)过程的时候,会看一看 ref属性是不是一个函数,如果是,就会调用这个函数,参数就是这个组件代表的 DOM 元素 。

当 reflnput 被调用时,参数 node 就是那个 input 元素, refinput 把这个 node 记录在成员变量 input 中 。

于是,当组件完成 mount 之后 ,就可以通过 this.input 来访问那个 input 元素。这是一个 DOM 元素,可以使用任何 DOMAPI 访问元素内容,通过访问 this.input.value 就可以直接读取当前的用户输入 。


todoList.js

首先来看mapStateToProps方法。

const selectVisibleTodos = (todos, filter) => {
  switch (filter) {
    case FilterTypes.ALL:
      return todos;
    case FilterTypes.COMPLETED:
      return todos.filter(item => item.completed);
    case FilterTypes.UNCOMPLETED:
      return todos.filter(item => !item.completed);
    default:
      throw new Error('unsupported filter');
  }
}

const mapStateToProps = (state) => ({
  todos: selectVisibleTodos(state.todos, state.filter)
})

Store 上的 filter 状态决定 to dos 状态上取哪些元素来显示,这个过程涉及对 filter 的 switch 判断。

首先来看mapDispatchToProps 方法。

const mapDispatchToProps = (dispatch) => {
  return {
    onToggleTodo: (id) => {
      dispatch(toggleTodo(id));
    },
    onRemoveTodo: (id) => {
      dispatch(removeTodo(id));
    }
  };
};

mapDispatchToProps 函数产生的两个新属性 onToggleTodo 和onRemoveTodo 的代码遵循一样的模式,都是把接收到的参数作为参数传递给一个 action构造函数,然后用 dispatch 方法把产生的 action 对象派发出去,这看起来是重复代码 。

实际上。 Redux 已经提供了一个 bindActionCreators 方法来消除这样的重复代码,显而易见很多 mapDispatchToProps 要做的事情只是把 action 构造函数和 prop 关联起来,所以直接以 prop 名为字段名,以 action 构造函数为对应字段值,把这样的对象传递给bindActionCreators 就可以

const mapDispatchToProps = (dispatch) => bindActionCreators({
  onToggleTodo: toggleTodo,
  onRemoveTodo: removeTodo
}, dispatch);

更进一步,可以直接让 mapDispatchToProps 是一个 prop 到 action 构造函数的映射,
这样连 bindActionCreators 函数都不用

const mapDispatchToProps = ({
  onToggleTodo: toggleTodo,
  onRemoveTodo: removeTodo
})

最后:
后面代码太多,不想继续写。可以查看git分支查看代码

你可能感兴趣的:(React)