5.3.1 两阶段选择过程
您可以在百度里搜索“深入浅出React和Redux 艾草文学(www.321553.xyz)”查找最新章节!
5.3.1 两阶段选择过程
既然这个selectVisibleTodos函数的计算必不可少,那如何优化呢?
实际上,并不是每一次对TodoList组件的重新渲染都必须要执行selectVisibleTodos中的计算过程,如果Redux Store状态树上代表所有待办事项的todos字段没有变化,而且代表当前过滤器的filter字段也没有变化,那么实在没有必要重新遍历整个todos数组来计算一个新的结果,如果上一次的计算结果被缓存起来的话,那就可以重用缓存的数据。
这就是reselect库的工作原理:只要相关状态没有改变,那就直接使用上一次的缓存结果。
reselect库被用来创造“选择器”,所谓选择器,就是接受一个state作为参数的函数,这个选择器函数返回的数据就是我们某个mapStateToProps需要的结果。
在前面的章节,我们已经强调过React组件的渲染函数应该是一个纯函数,Redux中的reducer函数也应该是一个纯函数,mapStateToProps函数也应该是纯函数,纯函数让问题清晰而且简化。不过,现在这个“选择器”函数可不是纯函数,它是一种有“记忆力”的函数,运行选择器函数会有副作用,副作用就是能够根据以往的运行“记忆”返回“记忆”中的结果。
reselect认为一个选择器的工作可以分为两个部分,把一个计算过程分为两个步骤:
步骤1,从输入参数state抽取第一层结果,将这第一层结果和之前抽取的第一层结果做比较,如果发现完全相同,就没有必要进行第二部分运算了,选择器直接把之前第二部分的运算结果返回就可以了。注意,这一部分做的“比较”,就是JavaScript的===操作符比较,如果第一层结果是对象的话,只有是同一对象才会被认为是相同。
步骤2,根据第一层结果计算出选择器需要返回的最终结果。
显然,每次选择器函数被调用时,步骤一都会被执行,但步骤一的结果被用来判断是否可以使用缓存的结果,所以并不是每次都会调用步骤二的运算。
选择器就是利用这种缓存结果的方式,避免了没有必要的运算浪费。
剩下的事情就是确定选择器步骤一和步骤二分别进行什么运算。原则很简单,步骤一运算因为每次选择器都要使用,所以一定要快,运算要非常简单,最好就是一个映射运算,通常就只是从state参数中得到某个字段的引用就足够,把剩下来的重活累活都交给步骤二去做。
在TodoList这个具体例子中,todos和filter的值直接决定应该显示什么样的待办事项,所以,很显然步骤一是获取todos和filter的值,步骤二就是根据这两个值进行计算。
使用reselect需要安装对应的npm包:
npm install –-save reselect
在src/todos/selector.js文件中,选择器函数的代码如下:
import {createSelector} from 'reselect';
import {FilterTypes} from '../constants.js';
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.UNCOMPPLETED:
return todos.filter(item => !item.completed);
default:
throw new Error('unsupported filter');
}
}
);
reselect提供了创造选择器的createSelector函数,这是一个高阶函数,也就是接受函数为参数来产生一个新函数的函数。
第一个参数是一个函数数组,每个元素代表了选择器步骤一需要做的映射计算,这里我们提供了两个函数getFilte和getTodos,对应代码如下:
const getFilter = (state) => state.filter;
const getTodos = (state) => state.todos;
上面说过,步骤一的运算要尽量简单快捷,所以往往一个Lambda表达式就足够。
createSelector函数的第二个参数代表步骤二的计算过程,参数为第一个参数的输出结果,里面的逻辑和之前TodoList中的逻辑没有什么两样,只是这第二个函数不是每次都会被调用到。
现在,我们可以在TodoList模块中改用新定义的选择器来获取待办事项数据了,代码如下:
import {selectVisibleTodos} from '../selector.js';
const mapStateToProps = (state) => {
return {
todos: selectVisibleTodos(state)
};
}
Redux要求每个reducer不能修改state状态,如果要返回一个新的状态,就必须返回一个新的对象。如此一来,Redux Store状态树上某个节点如果没有改变,那么我们就有信心这个节点下数据没有改变,应用在reselect中,步骤一的运算就可以确定直接缓存运算结果。
虽然reselect的createSelector创造的选择器并不是一个纯函数,但是createSelector接受的所有函数参数都是纯函数,虽然选择器有“记忆”这个副作用,但是只要输入参数state没有变化,产生的结果也就没有变化,表现得却类似于纯函数。
只要Redux Store状态树上的filter和todos字段不变,无论怎样触发TodoList的渲染过程,都不会引发没有必要的遍历todos字段的运算,性能自然更快。
虽然reselect库以re开头,但是逻辑上和React/Redux没有直接关系。实际上,在任何需要这种具有记忆的计算场合都可以使用reselect,不过,对于React和Redux组合的应用,reselect无疑能够提供绝佳的支持。 深入浅出React和Redux