Reselect核心API实战:createSelector从入门到精通
【免费下载链接】reselect 项目地址: https://gitcode.com/gh_mirrors/res/reselect
你是否遇到过这样的困扰:React组件因为频繁的状态计算而导致性能下降?当应用状态复杂时,每次状态更新都会触发大量重复计算,造成页面卡顿。Reselect的createSelector API正是解决这一问题的利器,它通过 memoization(记忆化)技术,确保只有当依赖数据变化时才重新计算结果,显著提升应用性能。本文将带你从基础到进阶,全面掌握createSelector的使用方法,让你的应用运行如丝般顺滑。
一、createSelector基础:什么是记忆化选择器
在Redux或React应用中,我们经常需要从状态(State)中派生数据。例如,从待办事项列表中筛选出已完成的项目。没有记忆化时,每次调用选择器函数都会重新计算,即使状态没有变化。
// 普通选择器 - 每次调用都会重新计算
const selectCompletedTodos = (state) => {
console.log('selector ran');
return state.todos.filter(todo => todo.completed);
};
// 连续调用3次,控制台会输出3次"selector ran"
selectCompletedTodos(state);
selectCompletedTodos(state);
selectCompletedTodos(state);
而使用createSelector创建的记忆化选择器,则会缓存计算结果。只有当依赖的输入选择器返回值发生变化时,才会重新计算:
// 记忆化选择器 - 仅在依赖变化时重新计算
import { createSelector } from 'reselect';
const selectTodos = state => state.todos;
const selectCompletedTodos = createSelector(
[selectTodos], // 输入选择器数组
todos => { // 结果函数
console.log('memoized selector ran');
return todos.filter(todo => todo.completed);
}
);
// 首次调用会计算并缓存结果
selectCompletedTodos(state); // 输出 "memoized selector ran"
// 后续调用直接返回缓存结果,不执行计算
selectCompletedTodos(state); // 无输出
selectCompletedTodos(state); // 无输出
通过对比可以发现,普通选择器每次调用都返回新数组(引用不同),而记忆化选择器在依赖不变时返回相同引用,这对React组件的重渲染优化至关重要:
// 普通选择器:每次返回新数组(引用不同)
console.log(selectCompletedTodos(state) === selectCompletedTodos(state)); // false
// 记忆化选择器:依赖不变时返回相同引用
console.log(selectCompletedTodos(state) === selectCompletedTodos(state)); // true
工作原理图解
记忆化选择器的核心原理是对输入选择器的结果进行缓存。当调用选择器时,createSelector会先检查输入选择器的返回值是否与上一次相同。如果相同,则直接返回缓存的结果;如果不同,则执行结果函数并更新缓存。
二、核心参数与使用方法
createSelector的参数结构灵活,支持多种调用方式。理解这些参数是掌握其用法的关键。
参数详解
| 参数名 | 描述 |
|---|---|
inputSelectors | 输入选择器数组,也可作为单独参数传递 |
resultFunc | 结果函数,接收输入选择器的返回值作为参数,返回最终计算结果 |
createSelectorOptions | 可选配置对象,用于自定义记忆化行为(如自定义比较函数、缓存大小等) |
基本用法示例
// 方式1:输入选择器作为数组参数
const selectA = state => state.a;
const selectB = state => state.b;
const selectC = createSelector(
[selectA, selectB], // 输入选择器数组
(a, b) => a + b // 结果函数:接收a和b,返回a+b
);
// 方式2:输入选择器作为单独参数
const selectC = createSelector(
selectA, selectB, // 输入选择器作为单独参数
(a, b) => a + b // 结果函数
);
带参数的选择器
有时需要根据动态参数筛选数据(如根据ID查询项目)。此时,输入选择器可以接收除state外的额外参数:
const selectTodoById = createSelector(
[
state => state.todos, // 第一个输入选择器:获取所有todos
(state, todoId) => todoId // 第二个输入选择器:获取参数todoId
],
(todos, todoId) => todos.find(todo => todo.id === todoId) // 结果函数:根据ID筛选
);
// 使用:传入state和参数todoId
const todo = selectTodoById(state, 1); // 获取ID为1的todo
三、高级特性:预定义类型与自定义配置
Reselect 5.1.0及以上版本提供了withTypes方法,支持预定义状态类型,简化TypeScript项目中的类型声明。同时,通过createSelectorCreator,可以自定义记忆化行为。
预定义状态类型(withTypes)
在TypeScript项目中,重复声明状态类型会导致冗余。使用withTypes可以一次性定义状态类型,后续无需重复声明:
// src/types.ts
export interface RootState {
todos: { id: number; completed: boolean }[];
alerts: { id: number; read: boolean }[];
}
// src/selectors.ts
import { createSelector } from 'reselect';
import { RootState } from './types';
// 创建预定义状态类型的createSelector
export const createAppSelector = createSelector.withTypes<RootState>();
// 使用时无需再次声明state类型
const selectTodoIds = createAppSelector(
[state => state.todos], // state自动推断为RootState
todos => todos.map(todo => todo.id)
);
自定义记忆化配置
通过createSelectorCreator,可以自定义记忆化函数(如使用LRU缓存)、设置缓存大小、自定义比较函数等:
import { createSelectorCreator, lruMemoize } from 'reselect';
import { shallowEqual } from 'react-redux';
// 创建自定义选择器创建器
const createAppSelector = createSelectorCreator({
memoize: lruMemoize, // 使用LRU缓存
memoizeOptions: {
maxSize: 10, // 缓存大小限制为10
equalityCheck: shallowEqual // 使用浅比较检查输入变化
},
argsMemoizeOptions: {
isEqual: shallowEqual // 对选择器参数使用浅比较
}
}).withTypes<RootState>(); // 结合withTypes预定义状态类型
// 使用自定义选择器
const selectReadAlerts = createAppSelector(
[state => state.alerts],
alerts => alerts.filter(alert => alert.read)
);
四、性能优化与最佳实践
1. 组合选择器
将复杂选择器拆分为多个简单选择器,利用记忆化特性减少重复计算:
// 基础选择器
const selectTodos = state => state.todos;
const selectFilter = state => state.filter;
// 中间选择器(记忆化)
const selectFilteredTodos = createSelector(
[selectTodos, selectFilter],
(todos, filter) => todos.filter(filter)
);
// 最终选择器(依赖中间选择器)
const selectFilteredTodoIds = createSelector(
[selectFilteredTodos], // 依赖记忆化的中间选择器
filteredTodos => filteredTodos.map(todo => todo.id)
);
2. 避免不必要的依赖
输入选择器数组应仅包含必要的依赖。过多的依赖会导致选择器更频繁地重新计算:
// 不佳:依赖整个state,任何state变化都会触发重新计算
const selectUser = createSelector(
[state => state], // 依赖整个state,不推荐
state => state.user
);
// 良好:仅依赖必要的state.user
const selectUser = createSelector(
[state => state.user], // 仅依赖user
user => user
);
3. 使用开发模式检查
Reselect提供开发模式检查,帮助发现常见问题(如结果函数返回新对象导致缓存失效):
const selectUser = createSelector(
[state => state.user],
user => ({ ...user, fullName: `${user.firstName} ${user.lastName}` }) // 返回新对象
);
// 开发模式下,开启inputStabilityCheck检查
if (process.env.NODE_ENV !== 'production') {
selectUser.devModeChecks = { inputStabilityCheck: 'always' };
}
五、实战案例:待办事项应用中的选择器设计
以下是一个完整的待办事项应用示例,展示如何使用createSelector优化状态派生:
项目结构
src/
├── store/
│ ├── state.ts # 状态类型定义
│ └── selectors.ts # 选择器定义
└── components/
└── TodoList.tsx # 使用选择器的组件
状态类型定义
// src/store/state.ts
export interface Todo {
id: number;
text: string;
completed: boolean;
category: string;
}
export interface RootState {
todos: Todo[];
filter: {
category: string | null;
showCompleted: boolean;
};
}
选择器实现
// src/store/selectors.ts
import { createSelector } from 'reselect';
import { RootState } from './state';
// 创建预定义类型的选择器创建器
export const createAppSelector = createSelector.withTypes<RootState>();
// 基础选择器
const selectTodos = createAppSelector(
state => state.todos
);
const selectFilter = createAppSelector(
state => state.filter
);
// 中间选择器:根据分类筛选
const selectTodosByCategory = createAppSelector(
[selectTodos, selectFilter],
(todos, filter) => {
if (!filter.category) return todos;
return todos.filter(todo => todo.category === filter.category);
}
);
// 最终选择器:根据完成状态筛选
export const selectVisibleTodos = createAppSelector(
[selectTodosByCategory, selectFilter],
(todos, filter) => {
if (!filter.showCompleted) return todos.filter(todo => !todo.completed);
return todos;
}
);
组件中使用
// src/components/TodoList.tsx
import { useSelector } from 'react-redux';
import { selectVisibleTodos } from '../store/selectors';
const TodoList = () => {
// 使用记忆化选择器
const todos = useSelector(selectVisibleTodos);
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
};
六、总结与进阶学习
createSelector是Reselect的核心API,通过记忆化技术显著提升应用性能。本文从基础用法、参数传递、高级特性到实战案例,全面介绍了其使用方法。关键要点包括:
- 记忆化原理:仅在输入选择器返回值变化时重新计算。
- 灵活参数:支持输入选择器数组或单独参数,以及动态参数。
- TypeScript支持:使用
withTypes预定义状态类型,简化类型声明。 - 性能优化:组合选择器、减少不必要依赖、使用开发模式检查。
进阶资源
- 官方文档:Reselect API 参考
- 源码学习:src/createSelectorCreator.ts
- 示例代码:docs/examples
通过合理使用createSelector,你可以构建出高性能、易维护的React/Redux应用。开始在你的项目中应用这些技巧,体验记忆化选择器带来的性能提升吧!
【免费下载链接】reselect 项目地址: https://gitcode.com/gh_mirrors/res/reselect
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考




