一 为组件准备数据
目前为止,我们借助于action和reducer将代码分解为可管理的各个部分。action可帮助模拟APP中发生的事情,reducer允许以集中的、可测试的方式将更新应用于状态。action和reducer还有另外两个方面的作用:通过减少视图负责的内容来清理视图,且有助于将APP中发生的不同类型的工作解耦并模块化。选择器可能比action和reducer略鲜为人知一些,但作为软件模式,它们提供类似的好处。
1.1 将redux 与 react 组件解耦
当不同实体对另一个实体的实现细节变得过于了解和依赖时,实体间通常被认为是紧耦合的。
解耦有一些重要的好处,比如对于更改的灵活性。
但如果在APP的不同部分需要使用相同的数据。就可能需要复制从redux获取或转换的逻辑了。一种更好的方法是,为组件定义更通用的属性,并且将任何为react从redux准备数据的逻辑抽取到某个地方。
在这种情况下,是 将redux store中数据的形式与最终要呈现数据的react组件解耦。react组件不必知道redux。可以数据源无关的方式编写组件,从而允许在不同的配置中使用它们。
或许某天对store做大重构。只通过保持组件通用,只需更新连接redux和react的衔接代码。也许需完全换掉redux,你也直接使用现有组件,而无需对UI代码进行大量修改。
组件通过connect函数的mapStateToProps函数与redux store交互。本章的目标是探索mapStateToProps背后的理念,如何有效地使用它,以及如何创建可复用的选择器函数,确保同样的工作只需做一次。
function mapStateToProps(state) {
const {tasks, isLoading, error} = state.tasks;
return {tasks, isLoading, error}
}
对于简单转换,上述就是所有内容。后面来讨论高级转换,可称为转换函数或选择器selector。
1.2 选择器概述
选择器就是一些函数,它们接收redux store中的状态并计算数据,这些数据最终会作为属性传递给React。它们是纯函数,意味着它们不会产生任何副作用; 这也使得它们易于记忆。
与所有编程概念一样,选择器的存在是为了解决问题。但问题是:没有选择器,组件将直接与redux store的结构想耦合。如果store的结构发生改变,就必须更新可能依赖于该结构的所有组件。
最终目标是:如果redux结构发生变化,无须更新任何组件。锁定组件级别的接口,并让上游代码处理数据结构的更改。不仅不需要更新组件以响应redux中的更改,且还意味着组件足够通用,可在不同上下文中使用。
选择器最大优点之一,是它们允许将可能的最小状态表示存储在redux中。选择器可用于计算派生数据,如过滤后的任务列表。
1.3 本地状态与redux状态
使用存储在本地状态中的搜索项来过滤任务是绝对可行的。但应该使用本地状态来处理吗?首先 ,使用本地状态要求组件计算应该渲染哪个任务,这样就将逻辑与组件耦合在一起了。请记住,使用redux的主要额外收益就是将逻辑与视图分离。其次,使用redux和选择器函数可获得性能提升。
1.4 编写自己的第一个选择器
本地状态示例的实现,redux对搜索项没有任何感知,所有必须在组件中完成过滤。使用redux的优点是,可在组件感知到过滤已发生之前就完成过滤操作。组件所要做的就是接收并渲染提供给它的任务列表。
src/App.js
function mapSatateToProps(state){
const {isLoading, error, searchTerm} state.tasks;
const tasks = state.tasks.filter(task => {
return task.title.match(new RegExp(searchTerm, 'i'));
})
return {tasks, isLoading, error}
}
这就起到解耦的作用。因为声明了redux和react之间的边界,所以可在将数据传入组件树之前更改数据的转换方式,而无须改变组件本身。通过在中间函数mapStateToProps中实现逻辑,可使组件更加通用和灵活。
虽然这是一种改进,但还有更多优化工作要做。如果一个组件需要6个选择器函数,该怎么办?mapStateToProps可能会膨胀到与组件本身一样大。如果想在多个连接的组件间直接复用选择器,又该怎么办?为每个组件写相同的逻辑是没有意义的。
一种流行的约定是从mapStateToProps函数中将选择器提取出来,并抽取到单独的、可复用的函数中。通常,这些选择器会列在与其领域最合适的reducer文件中。
1.3 reselect介绍
reselect库用于编写选择器:它还提供了一些关键的好处:记忆memoization和composition。
reselect 与 moization
memoization记忆是软件开发中听上去很复杂的术语。简而言之,memoization是指函数存储过去计算的结果,并用于将来的调用。
reselect 与 composition
composition含义是:使用reselect创建的选择器可链接在一起。一个选择器的输出可以是另一个选择器的输入。。
1.4 实现reselect
只影响与要实施的功能过更改相关的组件,这始终是最理想的情形。因此,可提前做好工作,使APP更加模块化。
src/App.js
function mapStateToProps(state){
const {isLoading, error} = state.tasks;
return {tasks: getFilteredTasks(state), isLoading, error}
}
src/reducers/index.js
import {createSelector} from 'reselect';
//...
const getTasks = state => state.tasks.tasks;
const getSearchTerm = state => state.tasks.searchTerm;
export const getFilteredTasks = createSelector(
[getTasks, getSearchTerm],
(tasks, searchTerm) => {
return tasks.filter(task => task.title.match(new RegExp(searchTerm, 'i')))
}
)
getTasks 和 getSearchTerm都被称为输入选择器。它们没有记忆功能,是简单的选择器,旨在用作前途记忆型选择器的输入。
getFilteredTasks是记忆型选择器,可使用createSelector函数创建。createSelector接收两个参数:输入选择器数组和转换函数。转换函数的实参将是每个输入选择器的结果。
这里就是选择器组合的实际应用。将getTasks和getSearchTerm用作getFilteredTasks的输入,并且可完全自由地将它们用作其他选择器的输入,甚至还可将getFilteredTasks用做其他选择器的输入。