引入
- 获取当前应该显示的待办事项,就是根据Redux Store状态树上的todos和filter两个字段上的值计算出来。但这个计算过程要遍历todos字段上的数组,当数组比较大的时候,对于TodoList组件的每一次重新渲染都重新计算一遍,就会显得负担过重了。
两阶段选择过程
- 如果Redux Store状态树上代表所有待办事项的todos字段没有变化,而且代表当前过滤器的filter字段也没有变化,那么实在没有必要重新遍历整个todos数组来计算一个新的结果
- reselect库的工作原理:只要相关状态没有改变,那就直接使用上一次的缓存结果
// src/todos/selector.js import {createSelector} from 'reselect'; import {FilterTypes} from '../constants.js'; /** * 从输入参数state抽取第一层结果,将这第一层结果和之前抽取的第一层结果做比较,如果发现完全相同,就没有必要进行第二部分运算了,选择器直接把之前第二部分的运算结果返回就可以了 */ const getFilter = (state) => state.filter; const getTodos = (state) => state.todos; // 第一个参数是一个函数数组,每个元素代表了选择器步骤一需要做的映射计算,这里我们提供了两个函数getFilte和getTodos export const selectVisibleTodos = createSelector( [getFilter, getTodos], (filter, todos) => { 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'); } } );
// todoList.js import React, {PropTypes} from 'react'; import {connect} from 'react-redux'; import TodoItem from './todoItem.js'; import {selectVisibleTodos} from '../selector.js'; const TodoList = ({todos, onClickTodo}) => { return ( <ul className="todo-list"> { todos.map((item) => ( <TodoItem key={item.id} id={item.id} text={item.text} completed={item.completed} /> )) } </ul> ); }; TodoList.propTypes = { todos: PropTypes.array.isRequired }; const mapStateToProps = (state) => { return { todos: selectVisibleTodos(state) }; } export default connect(mapStateToProps)(TodoList);
范式化状态树
- Redux Store 的状态树应该设计的尽量扁平且范式化
- 范式化:遵照关系型数据库的设计原则,减少冗余数据。范式化的数据结构设计就是要让一份数据只存储一份,数据冗余造成的后果就是难以保证数据一致性
- 反范式化:
反范式化数据结构的特点就是读取容易,修改比较麻烦。 - 范式化:
用一个typeId代表类型,然后在Redux Store上和todos平级的根节点位置创建一个types字段,内容是一个数组,每个数组元素代表一个类型,一个种类的数据是类似下面的对象:
当TodoItem组件要渲染内容时,从Redux Store状态树的todos字段下获取的数据是不够的,因为只有typeId。为了获得对应的种类名称和颜色,需要做一个类似关系型数据库的join操作,到状态树的types字段下去寻找对应typeId的种类数据。