Reselect核心API实战:createSelector从入门到精通

Reselect核心API实战:createSelector从入门到精通

【免费下载链接】reselect 【免费下载链接】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会先检查输入选择器的返回值是否与上一次相同。如果相同,则直接返回缓存的结果;如果不同,则执行结果函数并更新缓存。

Reselect记忆化原理

二、核心参数与使用方法

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预定义状态类型,简化类型声明。
  • 性能优化:组合选择器、减少不必要依赖、使用开发模式检查。

进阶资源

通过合理使用createSelector,你可以构建出高性能、易维护的React/Redux应用。开始在你的项目中应用这些技巧,体验记忆化选择器带来的性能提升吧!

【免费下载链接】reselect 【免费下载链接】reselect 项目地址: https://gitcode.com/gh_mirrors/res/reselect

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值