Redux 整合笔记四 为组件准备数据

397 阅读6分钟

一 为组件准备数据

目前为止,我们借助于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用做其他选择器的输入。