Redux性能优化5大技巧:用Selector选择器与记忆化解决组件重复渲染
2026/9/14 8:36:52 网站建设 项目流程

Redux性能优化5大技巧:用Selector选择器与记忆化解决组件重复渲染

【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux

Redux 是用于可预测全局状态管理的 JS 库,而Redux 性能优化的核心往往在于:如何用Selector 选择器与**记忆化(Memoization)**解决组件重复渲染的问题。本文面向新手,用 5 个可直接落地的技巧,带你避开"每发一个 action 就全页重渲染"的性能陷阱,让 React + Redux 应用保持流畅 ⚡

一、先搞清楚:为什么组件会"多余地"重复渲染?

在 Redux 的单向数据流中,每次dispatch一个 action,订阅了 store 的组件都会重新执行选择器函数。React-Redux 用===引用比较来判断是否需要重渲染——如果选择器每次都返回一个"新对象 / 新数组",哪怕数据内容一模一样,组件也会被强制重渲染。

这是 Redux 应用最常见的性能问题。官方 FAQ 也明确指出:

使用记忆化的选择器函数是重要的性能考量(Use of memoized selector functions is also an important performance consideration)。

📄 延伸阅读:docs/faq/Performance.md

二、技巧1:用 Selector 选择器封装状态读取,只取最小数据

选择器(Selector)是任何接收 Redux state 并返回其中某个值的函数,相当于"对状态的一次查询"。它有三个好处:

  • 🎯封装:只有 reducer 和 selector 知道状态结构,改动只改一处
  • ♻️复用:多个组件共享同一段取值逻辑
  • 📏最小化选择:组件只订阅真正需要的字段,而不是整个 slice

官方风格指南建议选择器统一命名为selectXxx,如selectTodoByIdselectVisibleTodos,并与 reducer 一起定义在 slice 文件中 📄 docs/style-guide/style-guide.md

// 直接查找——普通函数即可,无需记忆化 export const selectTodos = state => state.todos

记住第一条黄金法则:状态存得越少越好,能推导的值(过滤、求和、排序)就不要存进 state,而是用选择器现场推导。详见 docs/usage/deriving-data-selectors.md

三、技巧2:别让选择器"每次都返回新引用"

这是新手最容易踩的坑 ⚠️ 看下面这个 Todo 列表组件:

function TodoList() { // ❌ filter() 每次都产生新数组引用 → 每次 action 后都重渲染! const completedTodos = useSelector(state => state.todos.filter(todo => todo.completed) ) }

filter()map()、对象展开{...}这类操作每次都生成新引用,导致组件在每个 action 后都重渲染,即使数据根本没变。

React-Redux 在开发模式下还会主动警告你:

Selector unknown returned a different result when called with the same parameters. This can lead to unnecessary rerenders.

判断口诀:选择器返回的是"直接查找的原始引用或基础类型(数字、字符串、布尔)"→ 安全;返回"新数组/新对象"→ 必须处理。

四、技巧3:用 createSelector 记忆化昂贵选择器

记忆化是一种缓存:输入没变,就直接返回上次的结果,不重复计算。Redux 生态的标准工具是 Reselect 的createSelector(Redux Toolkit 已内置导出)。

下面这张 Profiler 截图展示了修复前的"灾难现场":只是刷新了通知列表,<UserPage>却因为非记忆化的filter选择器被白白重渲染了:

修复方式——把selectPostsByUser改写为记忆化选择器:

export const selectPostsByUser = createSelector( // 输入选择器:只负责"取原始值" [selectAllPosts, (state, userId) => userId], // 输出函数:负责"转换计算",仅当输入变化时才重跑 (posts, userId) => posts.filter(post => post.user === userId) )

原理很简单:createSelector会对比各输入选择器的结果,只要postsuserId都没变,就直接返回上次的缓存数组(引用不变,组件跳过渲染)。

📄 完整案例(含 Reselect 用法细节与常见错误):docs/usage/deriving-data-selectors.md

💡常见错误:输出函数直接返回输入(todos => todos)等于没做记忆化;另外createSelector默认缓存只保留最近一组输入,多组件用不同参数复用同一选择器时,需要用"选择器工厂"为每个组件创建独立实例。

五、技巧4:状态规范化,让"按 ID 查数据"变成 O(1)

当数据是长数组时,array.find()逐个遍历找某个 ID 会白白消耗 CPU。官方 FAQ 建议把状态存成**规范化(Normalized)**形状:{ ids: [], entities: {} }——ID 作键的查找表 + 一个排序好的 ID 数组。

Redux Toolkit 的createEntityAdapter帮你自动管理这套结构,并提供现成的selectAll/selectById选择器:

const postsAdapter = createEntityAdapter<Post>({ sortComparer: (a, b) => b.date.localeCompare(a.date) }) const { selectAll: selectAllPosts, selectById: selectPostById } = postsAdapter.getSelectors(state => state.posts)

好处有三:

  • 🔍 按 ID 直接取值,无需遍历
  • 📦 每条数据只存一份,省内存
  • 🧊 ID 数组只在增删或排序变化时才更新,天然利于减少重渲染

📄 规范化设计思路:docs/usage/structuring-reducers/NormalizingStateShape.md

六、技巧5:列表组件"父只传 ID、子自选数据",只渲染变化项

React 的默认行为是:父组件重渲染时,会递归渲染所有子组件。所以点一下某条帖子的"点赞"按钮,整条帖子列表里的所有子项都会跟着重渲染:

官方推荐的优化套路是:

  1. 列表父组件只选择ID 数组selectPostIds),ID 没变就不重渲染
  2. 每个列表项组件通过postId自己调用useSelector(selectPostById)取数据
  3. 再配合React.memo包裹子组件,确保 props 不变就不渲染

优化后的 Profiler 截图——只有被点的那一个组件渲染了:

📄 完整步骤(React.memo 选项、shallowEqual 比较等):docs/tutorials/essentials/part-6-performance-normalization.md

七、记忆化"平衡术":不是所有选择器都要缓存

过度优化同样是坑 ❌ 官方给出的判断标准:

场景是否记忆化
直接取值:state => state.todos❌ 不要,普通函数即可
推导但返回稳定值:求和、计数(返回数字)🤔 可选
每次返回新引用:map/filter生成新数组✅ 必须

给每个字段都写一个选择器、给每个选择器都套一层createSelector,只会增加维护成本,让 Redux 变成"Java getter 地狱"。只对"返回新引用"或"计算昂贵"的选择器做记忆化,这是最实用的平衡点。

八、常见错误速查清单 📋

  • ❌ 在useSelector里直接filter/map/ 展开对象 → 每次 action 后新引用 → 用createSelector记忆化
  • ❌ 选择器输出函数直接返回输入 → 记忆化失效
  • ❌ 把整个大 slice 一股脑选进组件 → 只选需要的字段
  • ❌ 多个组件共用一个带参数的记忆化选择器却传不同参数 → 缓存永远命中不了 → 用选择器工厂
  • ❌ 在 reducer 里做深拷贝(deep clone)→ 所有字段引用全变,触发大面积重渲染;不可变更新只需"浅拷贝受影响的路径"

九、总结

#技巧解决的问题
1Selector 选择器封装、最小化选择状态结构散落、组件订阅过多数据
2避免选择器返回新引用每个 action 后组件无脑重渲染
3createSelector记忆化昂贵计算重复执行 + 新引用强制渲染
4状态规范化(Entity Adapter)大数组遍历查找慢、内存冗余
5父传 ID + 子自选数据 + React.memo长列表"一人点赞全员重渲"

Redux 本身并不慢——状态树只是一个纯 JS 对象,真正拖慢应用的是"不必要的重复渲染"。掌握选择器与记忆化这两件武器,你的 Redux 应用就能在数据频繁更新的场景下依然保持丝滑 ✨

延伸阅读:

  • 选择器完整指南:docs/usage/deriving-data-selectors.md
  • 性能与规范化教程:docs/tutorials/essentials/part-6-performance-normalization.md
  • Redux 性能 FAQ:docs/faq/Performance.md
  • store 与 reducer 源码:src/createStore.ts、src/combineReducers.ts

【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询