本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
先前技术
Redux 拥有多元的技术渊源。它与某些模式和技术存在相似之处,但在关键方面也存在重要差异。下文我们将探讨这些相似点与不同点。
开发者体验
Dan Abramov(Redux 作者)在准备 React Europe 演讲 《支持时间旅行的热重载》期间开发了 Redux。他的目标是创建一个 API 极简但行为完全可预测的状态管理库。Redux 使得开发者无需额外适配即可实现日志记录、热重载、时间旅行、通用应用、录制与回放等功能。
Dan 在 Changelog 第 187 期节目中阐述了他的设计初衷和实现方法。
技术影响
Redux 在 Flux 思想基础上演进,同时借鉴 Elm 避开了其复杂性。即使您未曾使用过 Flux 或 Elm,也只需几分钟就能开始使用 Redux。
Flux
Redux 的灵感来源于 Flux 的几个重要特性。与 Flux 类似,Redux 要求将模型更新逻辑集中在应用的特定层(Flux 称为 "stores",Redux 称为 "reducers")。两者都建议通过名为 "action" 的普通对象来描述每次数据变更,而非允许应用代码直接修改数据。
与 Flux 不同,Redux 没有 Dispatcher 的概念。这是因为 Redux 依赖纯函数而非事件发射器,而纯函数易于组合且无需额外实体管理。根据您对 Flux 的理解,这既可视为偏离也可看作实现细节。Flux 常被描述为 (state, action) => state。就此而言,Redux 遵循了 Flux 架构的精髓,并借助纯函数使其更简洁。
另一重要区别是 Redux 假定您永不直接修改数据。您完全可以使用普通对象和数组作为状态,但强烈反对在 reducer 内部修改它们。应始终返回新对象,可通过对象展开运算符或 Immer 不可变更新库实现。
虽然在技术层面可能编写不纯的 reducer 来修改数据以应对极端性能场景,但我们强烈反对这种做法。这将导致时间旅行、录制/回放或热重载等开发特性失效。此外在大多数实际应用中,不可变性并不会引发性能问题——正如 Om 所证明的:即使需要分配新对象,但通过 reducer 纯度精确获知变更内容,可避免昂贵的重渲染和重新计算,整体仍能获益。
Elm
Elm 是受 Haskell 启发的函数式编程语言,由 Evan Czaplicki 创建。它强制采用“模型-视图-更新”架构,其更新函数签名为:(action, state) => state。Elm 的 "updaters" 与 Redux 中的 reducers 功能相同。
与 Redux 不同,Elm 是一门编程语言,因此天然具备强制纯函数、静态类型、开箱即用的不可变性以及模式匹配(通过 case 表达式实现)等优势。即使你不打算使用 Elm,也应该了解其架构设计并尝试实践。这里有一个有趣的 实现类似理念的 JavaScript 库实验场。Redux 应该从中汲取灵感!若想接近 Elm 的静态类型能力,可考虑 采用 Flow 这类渐进式类型方案。
Immutable
Immutable 是一个实现了持久化数据结构的 JavaScript 库。它性能优异且提供符合 JavaScript 习惯的 API。
(注:虽然 Immutable.js 曾启发 Redux,但目前我们推荐 改用 Immer 处理不可变更新)
Redux 并不关心状态存储形式——可以是普通对象、Immutable 对象或其他任何类型。 你可能需要(反)序列化机制来开发通用应用并从服务端注水状态,但除此之外,只要支持不可变性,任何数据存储库都可使用。例如在 Redux 状态中使用 Backbone 就不合理,因为 Backbone 模型是可变的。
注意:即使你的不可变库支持游标(cursor),也不应在 Redux 应用中使用。整个状态树应视为只读,状态更新必须通过 Redux 进行,同时通过订阅机制响应变更。因此游标写入对 Redux 无意义。若使用游标仅是为了解耦状态树与 UI 树并逐步优化,应改用选择器(selector)。 选择器是可组合的取值函数,可参考 reselect 这个出色的简洁实现。
Baobab
Baobab 是另一个通过不可变 API 更新普通 JavaScript 对象的流行库。虽然可与 Redux 共用,但协同效益有限。
Baobab 的核心功能是通过游标更新数据,而 Redux 强制要求仅能通过派发 action 更新数据。两者以不同方式解决相同问题,无法形成互补。
与 Immutable 不同,Baobab 底层未实现特殊高效数据结构,因此与 Redux 配合使用时并无额外优势。此类场景下直接使用普通对象更简便。
RxJS
RxJS 是管理异步应用复杂性的绝佳方案。事实上 已有项目尝试将人机交互建模为相互依赖的观察流。
Redux 与 RxJS 协同使用是否合理?当然!它们配合默契。例如将 Redux store 暴露为观察对象非常简单:
function toObservable(store) {
return {
subscribe({ next }) {
const unsubscribe = store.subscribe(() => next(store.getState()))
next(store.getState())
return { unsubscribe }
}
}
}
同样地,在将不同异步流馈送给 store.dispatch() 前,可将其组合转换为 action。
核心问题在于:若已使用 RxJS,是否还需 Redux?可能不必。用 RxJS 重实现 Redux 并不困难。有人称用 Rx 的 .scan() 方法两行即可实现——这完全可行!
如果你心存疑虑,不妨查看 Redux 的源代码(代码量其实不大)及其生态系统(例如开发者工具)。如果你对此不太在意,且希望全程采用响应式数据流,可以考虑尝试类似 Cycle 的方案,或将其与 Redux 结合使用。欢迎分享你的实践成果!
用户评价
“非常欣赏你在 Redux 上的工作成果” Flux 创始人 Jing Chen
“我在 Facebook 内部 JS 讨论组征求对 Redux 的意见,获得了全体成员的高度赞誉。这项作品令人惊叹。” Flux 文档作者 Bill Fisher
“你通过彻底革新而非改良 Flux 的方式创造出了更优秀的方案,这很酷。” Cycle 创始人 André Staltz
致谢
-
Elm 架构 提供了关于使用 reducers 进行状态建模的绝佳入门;
-
Turning the database inside-out 彻底颠覆了我的认知;
-
Developing ClojureScript with Figwheel 让我确信重新评估功能应该"开箱即用";
-
Webpack 实现了热模块替换功能;
-
Flummox 教会我如何实现无样板代码和单例的 Flux 方案;
-
disto 完成了可热更新 Store 的概念验证;
-
NuclearJS 证明了该架构的性能优势;
-
Om 推广了单一状态原子理念;
-
Cycle 展示了函数式解决方案的普适性;
-
React 带来了务实的技术创新。
特别感谢 Jamie Paton 贡献了 NPM 包名 redux。
赞助方
Redux 的初期研发由社区众筹支持。以下是为此做出卓越贡献的代表性企业:
查看完整的 Redux 原始赞助方列表,以及持续增长的 Redux 用户企业与个人名录。