Reselect 版本演进全解析:从 CHANGELOG 与源码看选择器库的核心 API 变迁
2026/9/20 10:14:16 网站建设 项目流程

Reselect 版本演进全解析:从 CHANGELOG 与源码看选择器库的核心 API 变迁

【免费下载链接】reselectSelector library for Redux项目地址: https://gitcode.com/gh_mirrors/re/reselect

Reselect 是 Redux 生态中最流行的选择器(Selector)库,其核心理念是"用记忆化(memoization)换取派生数据的计算效率"。本仓库根目录下的 CHANGELOG.md 记录了从 v1.0.0-alpha 到 v4.0.0 的完整变更历史,是理解 Reselect 设计演进的第一手资料。本文以该文档为骨架,结合 src 目录下的当前源码与 test 目录下的测试用例,逐版本还原createSelectorCreatorcreateStructuredSelectordefaultMemoize(现lruMemoize)等核心 API 的诞生与变革,帮你从"版本历史"视角建立对 Reselect 技术脉络的完整认知,并掌握这些 API 的正确用法。

说明:仓库中 v4.0.0 之后的变更记录已转移到 GitHub Release Notes,因此本文档聚焦 v4.0.0 及更早的演进;当前仓库版本为 5.3.0(见 package.json),文中将结合 website/docs/introduction/v5-summary.mdx 对后续变化做补充对照。

一、CHANGELOG 记录了哪些内容:先看整体脉络

CHANGELOG.md 开篇即说明两点关键信息:

  1. v4.0.0 及更早版本的变更记录在本文件中,之后的变更以 GitHub Release Notes 为准;
  2. 项目遵循语义化版本(Semantic Versioning),即:主版本号变化意味着不兼容的 API 变更,次版本号增加表示向后兼容的新功能,补丁版本号表示向后兼容的问题修复。

从记录来看,Reselect 的演进主线非常清晰:

版本时间核心变化
v1.0.0-alpha2015/09createSelectorCreator改为接收 memoize 函数;引入变参依赖
v1.0.0 / v1.1.02015/09正式版;新增createStructuredSelector
v2.0.02015/10校验输入选择器必须是函数
v2.1.0 ~ v2.5.42016新增resetRecomputationsresultFunc、TypeScript 类型、UMD/ES 构建,修复defaultMemoize若干 bug
v3.0.02017/03性能优化:输入引用相等(===)时不再重新计算
v4.0.02018/09暴露选择器依赖;支持为选择器提供自定义 memoize 函数;更新类型

下面按版本逐一展开。

二、v1.0.0-alpha:createSelectorCreator的里程碑式重构

2.1 从valueEqualsFunc到可插拔 memoize 函数

v1.0.0-alpha 引入了 Reselect 历史上最重要的一次破坏性变更:createSelectorCreator的参数从"自定义相等函数"改为"用户指定的 memoize 函数"。CHANGELOG 中给出了迁移前后对比:

变更前(传入自定义相等函数):

import { isEqual } from 'lodash' import { createSelectorCreator } from 'reselect' const deepEqualsSelectorCreator = createSelectorCreator(isEqual)

变更后(传入 memoize 函数):

import { isEqual } from 'lodash' import { createSelectorCreator, defaultMemoize } from 'reselect' const deepEqualsSelectorCreator = createSelectorCreator(defaultMemoize, isEqual)

这一设计的深远意义在于:Reselect 从此将"缓存策略"与"相等判断"解耦为两个正交维度,为后续自定义 memoizer(如 v5 中的weakMapMemoize)奠定了基础。当前源码中createSelectorCreator依然沿用了这一理念,签名已经演进为"既支持memoize函数 + 可变参数选项,也支持传入 options 对象",见 src/createSelectorCreator.ts 的重载定义。

2.2 变参依赖(Variadic Dependencies):两种写法并存

CHANGELOG 记录了 v1.0.0-alpha 的另一项新特性:选择器创建者既可以接收"参数数组",也可以接收"可变数量的独立参数"

变更前(数组形式):

const selector = createSelector( [state => state.a, state => state.b], (a, b) => a * b )

变更后(变参形式):

const selector = createSelector( state => state.a, state => state.b, (a, b) => a * b )

从当前源码看,这两种形式至今都被支持。核心实现在 src/utils.ts 的getDependencies函数中:createSelector内部先取出参数数组,若第一个参数本身是数组则直接使用,否则整个参数列表就是依赖;随后用assertIsArrayOfFunctions校验每个输入选择器都是函数,否则抛出TypeError——这正是 v2.0.0"创建时即校验参数"的实现基础。

2.3 在输入选择器中访问ownProps

同样是 v1.0.0-alpha,依赖函数"可以接收可变数量参数"的能力让选择器能够接收来自connectmapToProps传入的ownProps。CHANGELOG 给出的示例:

const selector = createSelector( state => state.a, (state, props) => state.b * props.c, (_, props) => props.d, (a, bc, d) => a + bc + d )

注意第二个和第三个输入选择器接收了(state, props)两个参数。这一能力到今天依然是 Reselect 配合 React-Redux 使用的核心场景,从 src/createSelectorCreator.ts 的dependenciesChecker实现可以看到:所有输入选择器都会用调用时的同一份参数arguments)逐一执行,因此只要把props作为第二个参数传入,依赖函数即可访问。

2.4 可配置的 memoize 函数(完整示例)

CHANGELOG 在 v1.0.0-alpha 中给出了"自定义 memoize 函数"的完整可运行示例,这也是createSelectorCreator的最典型用法:

import { createSelectorCreator } from 'reselect' import memoize from 'lodash.memoize' let called = 0 const customSelectorCreator = createSelectorCreator(memoize, JSON.stringify) const selector = customSelectorCreator( state => state.a, state => state.b, (a, b) => { called++ return a + b } ) assert.equal(selector({ a: 1, b: 2 }), 3) assert.equal(selector({ a: 1, b: 2 }), 3) assert.equal(called, 1) assert.equal(selector({ a: 2, b: 3 }), 5) assert.equal(called, 2)

这里createSelectorCreator(memoize, JSON.stringify)的含义是:用lodash.memoize记忆化 result 函数,并把JSON.stringify作为其"结果键解析器",从而把参数对象序列化为字符串作为缓存键。断言显示,相同参数第二次调用没有重新执行 result 函数(called仍为 1),换了参数后才重新计算(called变为 2)。

对照当前源码(src/createSelectorCreator.ts)可以看到:当第一个参数是函数时,内部会把它包装为{ memoize: memoizeOrOptions, memoizeOptions: memoizeOptionsFromArgs };当第一个参数是对象时,直接作为 options 使用。也就是说 v5 把这一旧式调用完整保留了下来,同时新增了createSelectorCreator({ memoize, memoizeOptions, argsMemoize, argsMemoizeOptions })的对象形式。

三、v1.1.0:createStructuredSelector的诞生

CHANGELOG 中 v1.1.0 的核心新特性是createStructuredSelector,一个解决"把多个选择器结果映射为对象"这一高频模式的便捷函数。文档指出,传给connect装饰器的选择器常常只是"把其他选择器的结果映射到对象键上",于是有了两种等价写法。

手动组合(繁琐):

const mySelectorA = state => state.a const mySelectorB = state => state.b const structuredSelector = createSelector( mySelectorA, mySelectorB, mySelectorC, (a, b, c) => ({ a, b, c }) )

使用createStructuredSelector(简洁):

const mySelectorA = state => state.a const mySelectorB = state => state.b const structuredSelector = createStructuredSelector({ x: mySelectorA, y: mySelectorB }) const result = structuredSelector({ a: 1, b: 2 }) // will produce {x: 1, y: 2}

其语义是:接收一个"键值均为输入选择器"的对象,返回一个结构化选择器,该选择器的结果是"键与原对象相同、值被替换为对应选择器计算结果"的对象

这一 API 的实现至今未变,见 src/createStructuredSelector.ts:实现先assertIsObject校验参数,再取Object.keys作为键列表,把所有选择器收集为dependencies数组,最后调用selectorCreator(dependencies, ...),其中 result 函数用reduce把各输入选择器的结果按原始键名组装回对象。因此createStructuredSelector本质上就是一个"自动生成组装对象 result 函数"的语法糖,其输出选择器同样具备记忆化能力。

四、v2.x:选择器自省能力与工程化完善

v2.x 阶段的主要工作集中在"选择器对象的自省 API"和"发布工程化"上,CHANGELOG 逐条记录如下:

  • v2.1.0:为选择器新增resetRecomputations方法,用于重置重计算计数;
  • v2.3.0:为选择器新增resultFunc属性,暴露组合函数本体;
  • v2.2.0 / v2.5.1 / v2.5.0 / v2.4.0:将 TypeScript 类型声明加入 package.json、修复类型破坏性变更、加入 ES 模块目录(es)、新增jsnext构建与 UMD 构建;
  • v2.5.2:修复defaultMemoize的一个 bug——当某组参数抛错后,再次以相同参数调用时选择器也应继续抛错(而不是返回缓存值);
  • v2.5.3:回退一个造成破坏性变更的 TypeScript 改动,留待主版本发布时重新引入;
  • v2.5.4:提升使用自定义相等检查时defaultMemoize的性能;
  • v2.0.0createSelectorcreateStructuredSelector及自定义 selector creator 在创建时校验输入选择器必须是函数,校验失败立即抛错以便获得有效堆栈;文档也提醒:这可能导致"包含从未被调用的错误选择器"的代码出现新的报错,属于潜在破坏性变更(概率较小)。

从当前源码可以完整看到这些能力的最终形态。createSelector返回的选择器对象上挂载了自省字段与方法(src/createSelectorCreator.ts):

Object.assign(selector, { resultFunc, // 组合函数本体 memoizedResultFunc, // 记忆化后的组合函数 dependencies, // 输入选择器数组 dependencyRecomputations: () => dependencyRecomputations, // 依赖重算次数 resetDependencyRecomputations: () => { dependencyRecomputations = 0 }, lastResult: () => lastResult, // 最近一次结果 recomputations: () => recomputations, // 组合函数重算次数 resetRecomputations: () => { recomputations = 0 }, memoize, // 使用的 memoize 函数 argsMemoize // 使用的 argsMemoize 函数 })

test/reselect.spec.ts 的测试用例正是围绕这些字段展开的:断言selector.recomputations()随调用次数精确变化、resetRecomputations能清零计数、resultFunc是函数、clearCache存在等。dependencyRecomputationsresetDependencyRecomputations则是 v5 才补充的(见 website/docs/introduction/v5-summary.mdx 的 "Additional Functionalities" 一节),它们配合argsMemoize让开发者能精确观测"输入选择器是否真的重新执行了"。

五、v3.0.0:引用相等检查带来的性能拐点

v3.0.0 是一次影响深远的性能优化:出于性能考虑,当输入按引用相等(===)时,选择器不再重新计算。CHANGELOG 用一段完整的示例演示了这一行为变化,并给出了 v2.5.4 与 v3.0.0 的输出对比。

示例(以现代import写法整理自 CHANGELOG;原文档中该示例同时混用了importrequire('./dist/reselect.js')两种模块写法,实际使用其一即可):

import { createSelector } from 'reselect' const mySelector = createSelector( state => state.values.filter(val => val < 5), values => { console.log('calling..') return values.reduce((acc, val) => acc + val, 0) } ) var state1 = { values: [1, 2, 3, 4, 5, 6, 7, 8, 9] } console.log(mySelector(state1)) state1.values = [3, 4, 5, 6, 7, 8, 9] console.log(mySelector(state1)) var state2 = { values: [1, 2, 3, 4, 5, 6, 7, 8, 9] } console.log(mySelector(state2)) var state3 = { values: [3, 4, 5, 6, 7] } console.log(mySelector(state3))

v2.5.4 的输出(每个状态对象都重新执行):

calling.. 10 calling.. 7 calling.. 10 calling.. 7

v3.0.0 的输出(引用相同则跳过):

calling.. 10 10 calling.. 10 calling.. 7

两相对比,差异一目了然:v3.0.0 中mySelector(state2)没有打印calling..,直接复用了state1.values修改前的缓存结果10。原因在于第一次调用state1时,state.values.filter(...)产生的数组与第二次调用(修改state1.values后)产生的新数组引用不同,会重算;而state2作为全新对象,其values数组在首次调用时与state1首次调用时产生的中间数组引用相等,因此直接命中缓存。

从当前源码看,这一"引用相等"的默认行为在lruMemoize(即原defaultMemoize)中通过referenceEqualityCheck实现(src/lruMemoize.ts),它是equalityCheck选项的默认值。值得一提的是,v5 将默认 memoizer 从lruMemoize切换为weakMapMemoize(见 website/docs/introduction/v5-summary.mdx 的 Breaking Changes 一节),后者同样基于严格引用相等,但从"只缓存最近一次结果"升级为"按参数引用建立缓存树"。

六、v4.0.0:依赖暴露与可插拔 memoize 的正式化

v4.0.0 是 CHANGELOG 记录的最后一个版本,包含两项新特性和一项破坏性变更:

  1. 暴露选择器依赖(Exposed selector dependencies, #251):即选择器对象上公开dependencies属性,开发者可以直接读取输入选择器数组;
  2. 为选择器提供自定义 memoize 函数(#297):允许在createSelector层面直接传入自定义 memoize;
  3. 破坏性变更:更新 TypeScript 类型(#274, #315)

"在createSelector层面直接自定义 memoize"这一能力在 v5 中被正式化为 options 对象参数。当前createSelector的类型签名(src/createSelectorCreator.ts)支持在第三个参数传入createSelectorOptions,其中可包含memoizememoizeOptionsargsMemoizeargsMemoizeOptions以及开发模式检查配置devModeChecks。例如,让某个选择器使用weakMapMemoize并启用结果相等检查:

import { createSelector, weakMapMemoize } from 'reselect' const selectItemsByCategory = createSelector( [ (state) => state.items, (state, category) => category ], (items, category) => items.filter(item => item.category === category), { memoize: weakMapMemoize, argsMemoize: weakMapMemoize } )

(完整示例同样出现在 src/weakMapMemoize.ts 的 JSDoc 中。)

从 src/createSelectorCreator.ts 的combinedOptions合并逻辑可以看到优先级规则:调用createSelector时直接传入的 options 优先于createSelectorCreator创建时设定的 options(通过对象展开实现覆盖)。同时,memoizeargsMemoize是两条独立的记忆化链路:memoize负责缓存 result 函数的结果,argsMemoize负责缓存"参数→输入选择器结果"的映射,前者在recomputationWrapper中计数重算次数,后者在dependenciesChecker中计数依赖重算次数。

七、源码印证:从历史 API 到当前实现的一脉相承

回顾 CHANGELOG 的每一条记录,都能在当前源码中找到对应实现,这正是 Reselect 十余年设计稳定性的体现:

CHANGELOG 中的特性当前源码位置当前 API 形态
createSelectorCreator接收 memoize 函数src/createSelectorCreator.ts函数式与 options 对象式双签名
变参依赖 / 数组依赖src/utils.ts两种形式并存,getDependencies统一处理
输入选择器必须是函数(v2.0.0)src/utils.tsassertIsArrayOfFunctions创建时抛错
createStructuredSelectorsrc/createStructuredSelector.ts基于createSelector的语法糖
resultFunc/resetRecomputationssrc/createSelectorCreator.ts附属于输出选择器对象
引用相等默认检查src/lruMemoize.tsreferenceEqualityCheck(v5 中由defaultEqualityCheck更名而来)
默认 memoize:defaultMemoizesrc/lruMemoize.tsv5 起更名lruMemoize,支持maxSize/resultEqualityCheck
为选择器提供自定义 memoize(v4.0.0)src/createSelectorCreator.tsoptions 对象中的memoize/memoizeOptions

在 v5 中,createSelector的默认 memoizer 是weakMapMemoize(见 src/createSelectorCreator.ts 的createSelectorCreator(weakMapMemoize)),它的缓存基于WeakMap参数树:对象参数按引用存入WeakMap、随垃圾回收自动释放,原始类型参数存入普通Map(src/weakMapMemoize.ts),并支持maxSize选项做"代际"有界缓存(src/weakMapMemoize.ts)。此外 v5 还引入了开发模式检查inputStabilityCheckidentityFunctionCheckcacheSizeCheck,默认均为once,可用 src/devModeChecks/setGlobalDevModeChecks.ts 的setGlobalDevModeChecks全局调整,或在createSelector的 options 中按选择器覆盖。

八、结语:如何用好这份版本历史

CHANGELOG.md 的价值在于它浓缩了 Reselect 设计决策的"为什么":

  • 为什么createSelectorCreator接收 memoize 函数?因为缓存策略应该是可插拔的,defaultMemoize不是唯一答案;
  • 为什么 v3.0.0 引入引用相等短路?因为性能优先——只要输入引用没变,结果必然没变,这是记忆化的最强前提;
  • 为什么选择器对象暴露resultFuncdependenciesrecomputations等字段?为了让选择器可测试、可观测、可调试。

如果你正在使用当前 v5.x 版本的 Reselect,这份历史记录能帮你理解现代 API 的设计根源:lruMemoize就是当年的defaultMemoizereferenceEqualityCheck就是当年的defaultEqualityCheckcreateSelectorCreator({ memoize, argsMemoize })则是 v1.0.0-alpha"可插拔 memoize"理念的最终形态。相关示例还可以进一步参考 docs/examples 目录下的可运行用例,例如 createStructuredSelector/modernUseCase.ts 展示了结构化选择器在现代 React 组件中的用法,selectorRecomputing.ts 则展示了重计算计数的实际应用。结合源码与测试阅读 CHANGELOG,是理解这个库最快、最准确的路径。

【免费下载链接】reselectSelector library for Redux项目地址: https://gitcode.com/gh_mirrors/re/reselect

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

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

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

立即咨询