项目地址:reduxjs/reselect
轮子作用:减少不必要的数据计算。
适用场景:从redux store中取数据的时候。
在一个大型的项目中,我们会在store中保存很多数据,同时也会有很多的大小组件。不同的组件需要不同的数据,所以我们保存在store中的数据往往是很完整和庞大的,在存进store之前都不会对从服务器请求回来的数据进行过多处理。
而我们每一个组件所需要的数据可能都是不同的,所以我们需要把store中的数据处理成组件所需要的数据。在这个过程中,我们可能需要遍历很多对象与数组,而这些计算在每次刷新的时候都需要进行一遍,哪怕触发刷新的是别的完全无关的操作。
reselect就是为了避免过多不必要的运算而诞生的轮子,它会缓存上次输入与输出,当它再次执行的时候会先检查一下函数的输入,如果与上次相同则避免计算,直接用上次的结果输出。
reselect还可以嵌套地写,来把一个数据变化的过程拆分成很多子过程,这些子过程也可以被别的reselect函数复用。而且任何一级的输入不变则后续的过程都会使用缓存输出。
以一个例子说明,假如我的state中有如下的数据:
state = {
students: [
{
name: 'tom',
age: 12,
},
{
name: 'mike',
age: 13,
},
]
}
复制代码
我有一个组件需要用到所有学生的名单,那么reselect的写法如下:
import { createSelector } from 'reselect'
// 从state中拿出学生数据
const studentsSelector = state => state.students
// createSelector的所有参数都是函数
// 前面的函数拥有相同的输入,比如都是state
// 最后一个函数的输入是前面所有函数的输出
// 最后一个函数的输出就是reselect函数的输出
const nameListSelector = createSelector(
studentsSelector,
students => students.map(student => student.name)
)
nameList = nameListSelector(state) // ['tom', 'mike']
复制代码
同时我还有一个组件要用到所有学生年龄的清单,那么我们只需要写:
const ageListSelector = createSelector(
studentsSelector,
students => students.map(student => student.age)
)
ageList = ageListSelector(state) // [12, 13]
复制代码
我们复用了前面的studentsSelector函数,让代码得到了简化。需要注意studentsSelector并不是一个reselect函数,所以不具备缓存输入输出的能力。
后面的nameListSelector与ageListSelector则是正统的reselect函数,可以在输入相同的情况下跳过计算,直接给出缓存的结果。这样写的好处会在下面的场景中显现:
- 如果此时我向state中添加了新的数据(比如教师档案,学生的档案并没有任何改变),那么所有从state中取数据的组件都会重新执行render函数。
- 如果没有reselect的缓存,所有类似
students.map(student => student.name)
的函数都会返回新的结果,因为map方法的特性就是会返回新的数组,从而触发后续内容都的重新渲染,即使前后的map方法返回的实际内容一样,因为数组的引用变化了,所以react就会认为它变了。 - 而使用reselect之后,因为studentsSelector这一级的输出没有变化,所以后续的输出都会使用缓存的上一次结果,这样组件就不会被重新渲染。
使用reselect函数可以很方便地完成从store数据到组件输入的转换,可以在各种重新渲染的时候避免不必要的计算从而提升性能。更加因为其复用的特性,减少了重复代码的书写,称得上是即高效又优雅。