jstips 实战:用 reselect 给 React 的 State to Props 映射加上记忆化
教程【免费下载链接】jstipsThis is about useful JS tips!项目地址https://gitcode.com/gh_mirrors/js/jstips点击查看免费下载本篇指南基于 jstips 仓库的 State to Props maps with memoryTip #66展开讲解在 React Redux 应用中如何借助reselect的createSelector为mapStateToProps里的状态计算函数注入“记忆”memoization让组件只在真正相关的 state 片段变化时才重新计算派生数据。读完本文你将掌握从朴素选择器到记忆化选择器的完整改造路径并理解其缓存失效的底层原理可直接迁移到自己的 Redux 容器组件中。问题背景组件频繁更新映射函数反复重算当你用 Redux 管理状态一段时间后通常会遇到这样的尴尬状态树state tree设计得很精细每个组件从mapStateToProps中拿到的都是“不多不少”恰好需要的那部分数据可组件依然频繁地“背后更新”——总是在映射mapping总是在计算calculating。原因在于在标准的 Redux 数据流中只要 store 派发了任意 action 导致 state 变化所有通过connect订阅的容器组件都会重新执行mapStateToProps。而mapStateToProps内部往往包含filter、map、reduce、排序等派生计算逻辑。即使这次变化与当前组件完全无关这些计算也会被白白重复执行一遍。朴素实现visibleTodos 选择器的问题原文档以经典的“TODOs 列表 可见性过滤”为例展示了容器组件中获取可见待办visible TODOs的朴素实现const getVisibleTodos (todos, filter) { switch (filter) { case SHOW_ALL: return todos case SHOW_COMPLETED: return todos.filter(t t.completed) case SHOW_ACTIVE: return todos.filter(t !t.completed) } } const mapStateToProps (state) { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } }这个版本的痛点非常明确每次组件更新时getVisibleTodos都会被重新计算一遍。todos.filter(...)需要遍历整个数组如果待办列表很大或筛选逻辑更复杂多层过滤、聚合、排序重复计算的开销会随渲染频率线性放大。更关键的是这种重复计算往往是无意义的如果state.todos和state.visibilityFilter都没变那么getVisibleTodos的结果必然与上次相同完全可以复用上次的计算结果。改造第一步拆出可追踪的状态选择器要让计算具备“记忆”前提是先定义清楚“哪些状态片段是本次计算真正依赖的”。reselect 的做法是先写出一组原子级的选择器selector每个选择器只负责从 state 树中取出一个片段const getVisibilityFilter (state) state.visibilityFilter const getTodos (state) state.todos这两个函数就是可select的输入选择器input selectors。它们只做属性读取不做任何派生计算因此开销极小。它们的价值在于把getVisibleTodos的“输入依赖”显式声明出来——它只关心visibilityFilter和todos这两个片段。改造第二步用 createSelector 赋予函数记忆接下来引入reselect的createSelector把输入选择器与结果计算函数组合起来import {createSelector} from reselect const getVisibleTodos createSelector( [getVisibilityFilter, getTodos], (visibilityFilter, todos) { switch (visibilityFilter) { case SHOW_ALL: return todos case SHOW_COMPLETED: return todos.filter(t t.completed) case SHOW_ACTIVE: return todos.filter(t !t.completed) } } )createSelector接受两个参数第一个是输入选择器数组或单个选择器第二个是结果计算函数result function。计算函数的参数顺序与输入选择器数组一一对应第一个输入选择器的结果作为第一个参数visibilityFilter第二个输入选择器的结果作为第二个参数todos。说明原文档中SHOW_COMPLETED分支存在一处笔误todos.filtler(...)上文已修正为filter实际使用时请以修正版为准。此时getVisibleTodos的行为发生了本质变化它内部维护了一份缓存。每次调用时依次执行输入选择器取出当前state.visibilityFilter与state.todos将结果与上次缓存的值做引用比较即 Object.is 级别的引用相等性判断若所有输入值都未变化直接返回上次缓存的结果完全跳过switch/filter计算只要任一输入值发生变化才重新执行结果计算函数并用新结果刷新缓存。这就是文档所说的“给函数记忆”reselect 让getVisibleTodos自己知道它关心的 state 部分是否变化——变了才继续处理没变就直接返回上次结果。改造第三步mapStateToProps 传入完整 state由于createSelector返回的getVisibleTodos现在只接收一个state参数输入选择器会自行完成内部拆解mapStateToProps也要相应简化const mapStateToProps (state) { return { todos: getVisibleTodos(state) } }改造完成。现在每当 store 派发 action若visibilityFilter或todos引用未变 →getVisibleTodos直接返回缓存的数组引用connect层比较 props 时发现todos引用未变就不会触发该容器组件重新渲染前提是其他 mapStateToProps 结果也没变若相关片段变了 → 重新计算并返回新数组引用组件正常更新。也就是说“不涉及的部分 state 变化时不再有任何额外计算”这正是原文档给出的核心结论No extra calculations if the involved parts of the state havent changed!原理纵深为什么引用比较能成立createSelector的记忆化之所以可靠依赖于 Redux 状态管理的一条核心约定不可变更新。Reducer 每次返回的都是新的 state 对象未受影响的子树会保持原引用不变。因此修改某条 todo 的completed字段 →todos数组会生成新引用 → 触发getVisibleTodos重算只修改与列表无关的其他 state 片段如currentUser.name→todos与visibilityFilter的引用都不变 → 直接命中缓存。这正是“引用相等”能充当“内容是否变化”的代理的原因。若代码中存在原地 mutate如todos.push(...)新旧引用相同记忆化就会失效或返回陈旧结果——这也是 reselect 这类工具反过来倒逼你保持不可变数据流的原因。从本仓库其他文章也可以印证这一数据流设计思路Why you should use Object.is() in equality comparison-in-equality-comparison.md) 讨论了引用/值比较的语义差异而 reselect 的缓存命中正是建立在这种精确比较之上。进阶实践选择器组合与多参数派生createSelector最有价值的特性是可组合性记忆化选择器可以继续作为其他选择器的输入。例如在上面的 TODOs 例子上继续派生统计信息const getVisibleTodos createSelector( [getVisibilityFilter, getTodos], (visibilityFilter, todos) { /* 见上文实现 */ } ) const getCompletedTodoCount createSelector( [getVisibleTodos], (visibleTodos) visibleTodos.filter(t t.completed).length )此时getCompletedTodoCount依赖的是记忆化后的getVisibleTodos只要原始 state 片段未变getVisibleTodos命中缓存getCompletedTodoCount也不会重算。这种逐层缓存让大型应用的派生数据流变成一张“只有受影响分支才会被触发计算”的图而不是每次 dispatch 全量重跑。此外createSelector还支持为结果计算函数附加参数。当过滤条件不来自 store 而来自组件 props 时可以将 props 传入mapStateToProps的第二个参数并在结果函数中接收再通过createSelector的第二个参数传入自定义缓存大小或序列化比较函数。这些都属于 reselect 的扩展 API建议结合实际项目需要选用。与 React 性能主题的呼应记忆化选择器解决的是“派生计算重复执行”的问题而 jstips 仓库的 React 系列还有其他几篇与之互补的性能视角Check the reason make your page re-render by changed props and stateTip #74提供了一套在componentDidUpdate/useEffect中打印“哪些 props/state 变了”的追踪方案可以用来定位是否因mapStateToProps返回了新引用例如每次返回未缓存的新数组导致无谓重渲染——这与本文的记忆化改造正好是“诊断 根治”的配套组合Upping Performance by Appending/KeyingTip #75讲解 React reconciliation 与列表key的关系说明列表子组件复用而非重建对渲染性能的影响。当mapStateToProps每次都返回新数组引用时即便内容相同React 也会因引用变化重新协调子组件——记忆化选择器正是阻断这条链路的关键一环Keys in children components are importantTip #02则从子组件身份识别的角度提醒从数组中动态创建的组件必须携带稳定、唯一的key否则即使父级 props 引用优化到位子组件仍可能被错误重建。使用注意与适用边界记忆化并非万能以下几点需要注意输入必须稳定可比较输入选择器返回的应是引用稳定的值来自不可变 state 的对象、数组、基本类型。若某个输入选择器每次返回新建的对象字面量如(state) ({...state.settings})引用永远不同缓存将永远失效记忆化退化为纯开销。结果函数的返回引用SHOW_ALL分支直接返回todos原引用天然可复用而filter总会产生新数组只有输入未变时才会复用缓存。设计选择器时留意这一点能更好地配合connect的浅比较。缓存粒度reselect 默认只缓存最近一次的结果部分版本可通过配置调整。输入组合多、结果计算重的场景应把大计算拆成多层小选择器让每一层都能独立命中缓存。安装与运行环境reselect是独立于 React 与 Redux 的纯 JS 库需要单独安装如npm install reselect后通过模块系统引入jstips 仓库本身是文档型仓库关联文档及其繁体中文版均以代码示例说明用法不携带可执行工程实际落地时请结合你自己的 React Redux 项目环境验证。小结本文完整呈现了“给 state 到 props 的映射加记忆”的改造三步曲用朴素函数写getVisibleTodos→ 每次渲染都重算拆出getVisibilityFilter、getTodos两个原子选择器用createSelector组合出记忆化版本mapStateToProps只传完整state。改造后未涉及的 state 变化不再触发派生计算组件更新频率与计算开销都回归到“需要时才发生”。配合本仓库中关于 re-render 追踪、reconciliation 与 key 的系列文章你可以系统性地把 React Redux 应用的渲染性能理顺。赞分享教程【免费下载链接】jstipsThis is about useful JS tips!项目地址https://gitcode.com/gh_mirrors/js/jstips点击查看免费下载相关推荐3 步装好网盘直链下载助手8 家网盘解析直链直喂 IDM 和 Aria23 步装好网盘直链下载助手8 家网盘解析直链直喂 IDM 和 Aria2 你在网盘网页版点下载要么被限速到 KB 级要么要先过一遍广告页。 网盘直链下载前端Mitosis 组件规范深入解析 no-assign-props-to-state 规则——为什么不能把 props 直接赋值给 stateMitosis 组件规范深入解析 no assign props to state 规则——为什么不能把 props 直接赋值给 state 本篇技术指南聚焦前端代码生成开发工具低代码react-redux-typescript-guide选择器组合使用reselect创建记忆化选择器react redux typescript guide选择器组合使用reselect创建记忆化选择器 在React Redux应用开发中随着状态管理复杂度前端教程上一篇LinkSwift九大网盘直链提取与自动化下载的专业解决方案下一篇突破网盘限速7天精通直链下载的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

InfiniBand 1.7规范实战解读:架构、版本演进与避坑指南

InfiniBand 1.7规范实战解读:架构、版本演进与避坑指南

简介:IB架构规范卷1第1.7版(2023年7月11日最终版)是IBTA发布的权威技术文档,用于定义InfiniBand网络体系结构,特别适合高性能计算、数据中心、存储网络领域的工程师和架构师阅读。该资源只有一个PDF文件,整…

2026/10/13 21:26:18 阅读更多 →
华为S5700交换机VLAN配置实战:PVID、Trunk与VLAN间通信避坑指南

华为S5700交换机VLAN配置实战:PVID、Trunk与VLAN间通信避坑指南

简介:这份PDF资料聚焦华为S5700三层交换机的VLAN配置,面向网络运维初学者与需要快速上手的网络技术人员,帮助解决VLAN划分、管理IP设置及与路由器互联等实际问题。资源为1个PDF文件,压缩包约1.43MB,内容以图文与命令示…

2026/10/9 14:46:00 阅读更多 →
Hyperf 控制器(Controller)开发指南:路由绑定、请求响应注入与协程安全实践

Hyperf 控制器(Controller)开发指南:路由绑定、请求响应注入与协程安全实践

后端Web框架微服务RPC框架异步编程 【免费下载链接】hyperf 🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease. 项目地址: https://gitcode.com/hyperf/hyperf 点击查看 免费下载 …

2026/10/13 9:41:33 阅读更多 →

最新新闻

C++字符类型char与wchar_t深度解析

C++字符类型char与wchar_t深度解析

1. 理解C中的字符类型本质在C开发中,字符处理是每个程序员都会遇到的基础问题。char和wchar_t看似简单,但背后涉及字符编码、国际化支持等复杂概念。我们先从内存层面理解它们的物理差异:char类型保证至少8位宽度,这源于C语言的遗…

2026/10/14 11:01:09 阅读更多 →
LangGraph工具调用与菜单RAG:语音助手菜单查询落地实践

LangGraph工具调用与菜单RAG:语音助手菜单查询落地实践

语音助手的对话流里,用户说“帮我查一下附近有哪些川菜馆”,系统到底是怎么把“川菜馆”这个模糊的口语表达,变成一次数据库查询,再把查询结果组织成一句像样的人话返回去的?前面几篇我们聊了LangGraph的状态管理和多轮…

2026/10/14 11:01:09 阅读更多 →
第十一届蓝桥杯B组C/C++省赛全题解:从填空到DP的完整思路

第十一届蓝桥杯B组C/C++省赛全题解:从填空到DP的完整思路

参加过第11届蓝桥杯B组省赛的老选手应该都有同感:这届比赛原本定在4月份,因为疫情硬是拖到了7月,原本的线下笔试改成线上双机位监考。很多人的复习节奏完全被打乱,刷题计划和心态都受影响。但拿题目本身来说,B组的C/C卷…

2026/10/14 11:01:09 阅读更多 →
Data+AI一体化平台实战:智能目录、增强开发与MLOps落地指南

Data+AI一体化平台实战:智能目录、增强开发与MLOps落地指南

原生标题太长也太营销化了,我帮你提炼成真实博主口吻的硬核标题,再按规范生成一篇完全去平台化的深度博文。以下是正文直接输出。1. 不只是提效工具:一体化数据智能平台到底解决什么问题先说结论再展开。我们团队真实数据栈曾经是典型的三层饺…

2026/10/14 11:01:09 阅读更多 →
StarModAPI 在 StarMade 服务器中的实现:加载器接入、事件注册与性能优化

StarModAPI 在 StarMade 服务器中的实现:加载器接入、事件注册与性能优化

简介:StarMod 是一份面向 Java 服务端开发者的开源项目源码,基于 StarModAPI 为沙盒太空模拟游戏《StarMade》提供可扩展、可定制的服务器实现,适合希望深入理解游戏服务器架构、网络通信与插件机制的中高级开发者研读。压缩包共 66 个文件&a…

2026/10/14 11:01:09 阅读更多 →
自研可逆字符串长度压缩算法:设计思路与工程实现

自研可逆字符串长度压缩算法:设计思路与工程实现

做这个项目的起因其实挺有意思。有一次我在处理一批数据库导出的文本字段,中间层要求把每条记录的长度压到某个阈值以下,但又不能让数据丢失,因为下游还要做精确校验。当时我翻遍了现成的压缩库,要么是有损的哈希方案,…

2026/10/14 11:00:07 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →