Redux 的(简要)历史
¥A (Brief) History of Redux
2011:JS MVC 框架
¥2011: JS MVC Frameworks
早期的 JavaScript MVC 框架(例如 AngularJS、Ember 和 Backbone)存在问题。AngularJS 尝试强制将 "controllers" 与模板分离,但没有什么可以阻止你在模板中编写 <div onClick="$ctrl.some.deeply.nested.field = 123">
。同时,Backbone 是基于事件触发器的 - 模型、集合和视图都能够触发事件。模型可能会发出 "change:firstName"
事件,视图将订阅这些事件。但是,任何代码都可以订阅这些事件并运行更多逻辑,这可能会触发更多事件
¥Early JavaScript MVC frameworks like AngularJS, Ember, and Backbone had issues. AngularJS tried to enforce separation of "controllers" from templates, but nothing prevented you from writing <div onClick="$ctrl.some.deeply.nested.field = 123">
in a template. Meanwhile, Backbone was based on event emitters - Models, Collections, and Views were all each capable of emitting events. Models might emit a "change:firstName"
event, and Views would subscribe to those. But, any code could subscribe to those events and run more logic, which could trigger more events
这使得这些框架非常难以调试和维护。更新一个模型中的一个字段可能会触发应用周围运行的数十个事件和逻辑,或者任何模板都可以随时更改状态,这使得你无法理解当你进行状态更新时会发生什么 。
¥That made these frameworks very hard to debug and maintain. It was possible that updating one field in one model could trigger dozens of events and logic running around the app, or that any template could make changes to state at any time, which made it impossible to understand what would happen when you did a state update.
2014:通量
¥2014: Flux
早在 2012-2013 年左右,当 React 首次公开发布时,Facebook 已经在内部使用它好几年了。他们遇到的问题之一是,他们的 UI 中有多个独立部分需要访问相同的数据,例如 "有多少条未读通知",但他们发现在使用 Backbone 风格的代码时很难保持逻辑清晰。
¥Back around 2012-2013, when React was first publicly released, Facebook had been using it internally for a couple years. One of the problems they ran into was that they had multiple independent pieces of their UI that needed access to the same data, like "how many unread notifications are there", but they found it hard to keep that logic straight when using Backbone-style code.
Facebook 最终提出了一种名为 "通量" 的模式:创建多个单例 Store,例如 PostsStore
和 CommentsStore
。每个 Store 实例都会向 Dispatcher
注册,触发 Store 更新的唯一方法是调用 Dispatcher.dispatch({type: "somethingHappened"})
。那个普通的对象被称为 "action"。这个想法是所有状态更新逻辑都是半集中的 - 你不能让应用的任何随机部分改变状态,并且所有状态更新都是可预测的。
¥Facebook ultimately came up with a pattern called "Flux": create multiple singleton Stores, like a PostsStore
and CommentsStore
. Each of those Store instances would register with a Dispatcher
, and the only way to trigger an update in a store was to call Dispatcher.dispatch({type: "somethingHappened"})
. That plain object was called an "action". The idea was that all the state update logic would be semi-centralized - you couldn't just have any random part of the app mutate state, and all the state updates would be predictable.
Facebook 在 2014 年左右宣布了这个 "通量架构" 概念,但没有提供实现该模式的完整库。这导致 React 社区构建了数十个受 Flux 启发的库,这些库具有不同的模式。
¥Facebook announced this "Flux Architecture" concept around 2014, but didn't provide a full library that implemented that pattern. That led the React community to build dozens of Flux-inspired libraries with variations on the pattern.
2015:Redux 的诞生
¥2015: The Birth of Redux
2015 年中,Dan Abramov 开始构建另一个受 Flux 启发的库,称为 Redux。这个想法是为了展示 "时间旅行调试" 的 会议演讲。该库被设计为使用 Flux 模式,但应用了一些函数式编程原则。你可以使用执行不可变更新的可预测 reducer 函数,而不是 Store 实例。这将允许及时来回跳跃,以了解该状态在各个方面的看法。它还将使代码更加简单、可测试和易于理解。
¥In mid-2015, Dan Abramov began building yet another Flux-inspired library, called Redux. The idea was to demonstrate "time-travel debugging" for a conference talk. The library was designed to use the Flux pattern, but with some functional programming principles applied. Rather than Store instances, you could use predictable reducer functions that did immutable updates. This would allow jumping back and forth in time to see how the state looked at various points. It would also make the code more straightforward, testable, and understandable.
Redux 于 2015 年问世,并很快淘汰了所有其他受 Flux 启发的库。它得到了 React 生态系统中高级开发者的早期采用,到 2016 年,很多人开始说 "如果你使用 React,那么你也必须使用 Redux"。(坦率地说,这导致很多人在不需要使用 Redux 的地方使用它!)
¥Redux came out in 2015, and quickly killed off all the other Flux-inspired libraries. It got early adoption from advanced developers in the React ecosystem, and by 2016, many people began to say that "if you're using React, you must be using Redux too". (Frankly, this led to a lot of people using Redux in places they didn't need to be using it!)
还值得注意的是,当时,React 只有其旧版的 Context API,该 API 基本上已被破坏:它无法正确传递更新的值。因此,可以将事件触发器放入上下文中并订阅它们,但你不能真正将其用于纯数据。这意味着很多人开始采用 Redux,因为它是一种在整个应用中持续传递更新值的方法。
¥It's also worth noting that at the time, React only had its legacy Context API, which had was basically broken: it couldn't properly pass updated values down. So, it was possible to put event emitters into Context and subscribe to them, but you couldn't really use it for plain data. That meant that a lot of people began adopting Redux because it was a way to consistently pass updated values around the entire application.
Dan 很早就说过“Redux 并不意味着是编写代码的最短方式 - 它的目的是使其可预测和可理解”。其中一部分是关于拥有一致的模式(状态更新由 reducer 完成,因此你始终查看 reducer 逻辑以了解状态值可以是什么,可能的操作是什么以及它们导致什么更新)。它还涉及将逻辑移出组件树,以便 UI 主要显示 "这件事发生了",并且你的组件更简单。除此之外,编写为 "纯函数" 的代码(如化简器和选择器)更容易理解:争论进来,结果出来,没有什么可看的。最后,Redux 的设计启用了 Redux DevTools,它向你显示已分派的所有操作的可读列表、操作/状态包含的内容以及每个操作发生的更改。
¥Dan said early on that "Redux is not meant to be the shortest way to write code - it's meant to make it predictable and understandable". Part of that is about having a consistent pattern (state updates are done by reducers, so you always look at the reducer logic to see what the state values can be, what the possible actions are, and what updates they cause). It's also about moving logic out of the component tree, so that the UI mostly just says "this thing happened", and your components are simpler. Along with that, code that is written as "pure functions", like reducers and selectors, are more straightforward to understand: arguments in, result out, nothing else to look at. Finally, Redux's design enabled the Redux DevTools, which show you a readable list of all the actions that were dispatched, what the actions/state contained, and changes occurred for each action.
早期的 Redux 模式尤其是样板代码。通常有 actions/todos.js
、reducers/todos.js
和 constants/todos.js
,只是为了定义单个动作类型 (const ADD_TODO = "ADD_TODO"
)、动作创建者函数和 reducer 案例。你还必须使用扩展运算符手写不可变的更新,这很容易搞砸。人们在 Redux 中获取和缓存服务器状态,但是需要大量手动编写代码来编写 thunk 来进行获取、使用获取的数据分派操作以及管理 reducer 中的缓存状态。
¥The early Redux patterns were especially boilerplate-heavy. It was common to have actions/todos.js
, reducers/todos.js
, and constants/todos.js
, just to define a single action type ( const ADD_TODO = "ADD_TODO"
), action creator function, and reducer case. You also had to hand-write immutable updates with spread operators, which were easy to mess up. People did fetch and cache server state in Redux, but it took a lot of manually-written code to write thunks to do the fetching, dispatch the actions with the fetched data, and manage the cache status in the reducers.
尽管有这些样板,Redux 仍然很流行,但它始终是最大的关注点。
¥Redux became popular in spite of that boilerplate, but it was always the biggest point of concern.
2017:生态系统竞争
¥2017: Ecosystem Competition
到 2017-18 年,情况发生了变化。许多社区现在更多地关注 "数据获取和缓存" 而不是 "客户端状态管理",就在那时我们看到了 Apollo、React Query、SWR 和 Urql 等用于数据获取的库的兴起。与此同时,我们还推出了新的 React Context API,它可以正确地将更新的值沿着组件树传递。
¥By 2017-18, things had changed. A lot of the community was now focusing more on "data fetching and caching" rather than "client-side state management", and that's when we saw the rise of libraries like Apollo, React Query, SWR, and Urql for data fetching. At the same time, we also had the new React Context API came out, which does properly pass updated values down the component tree.
这意味着 Redux 不再像以前那样 "required" - 现在有其他工具可以解决许多相同的问题,但重叠程度各不相同(并且通常需要更少的代码)。对 "boilerplate" 的频繁抗诉也引起了 Redux 用户的很多关注。
¥That meant that Redux wasn't nearly as "required" as it used to be - there were now other tools that solved many of the same problems, with varying amounts of overlap (and often with less code). The frequent complaints about "boilerplate" also caused a lot of concern from folks using Redux.
2019:Redux 工具包
¥2019: Redux Toolkit
因此,在 2019 年,我们构建并发布了 Redux Toolkit,作为一种更简单的方法,用更少的代码编写相同的 Redux 逻辑。RTK 仍然是 "Redux"(单一存储,通过不可变的更新逻辑调度操作来触发 reducer 中完成的状态更新),但具有更简单的 API 和更好的内置默认行为。其中还包括 RTK Query,这是我们的内置数据获取和缓存库,其灵感来自 React Query 和 Apollo。
¥So, in 2019, we built and shipped Redux Toolkit as a simpler way to write the same Redux logic with less code. RTK is still "Redux" (single store, dispatching actions to trigger state updates done in reducers via immutable update logic), but with a simpler API and better built-in default behaviors. That also includes RTK Query, our built-in data fetching and caching library that was inspired by React Query and Apollo.
今天,RTK 是编写 Redux 逻辑的标准方式。与所有工具一样,它也有权衡。RTK 可能会比 Zustand 使用更多的代码,但它也提供了将应用逻辑与 UI 分离的有用模式。Redux 并不是适合每个应用的工具,但它仍然是 React 应用中使用最广泛的状态管理库,拥有优秀的文档,并提供了大量功能来帮助你构建具有一致和可预测结构的应用。
¥Today, RTK is the standard way to write Redux logic. Like all tools, it has tradeoffs. RTK is probably going to be a bit more code to use than Zustand, but it also provides useful patterns for separating app logic from the UI. Redux isn't the right tool for every app, but it is still the most widely used state management lib with React apps, has excellent documentation, and provides a lot of features to help you build apps with a consistent and predictable structure.
更多信息
¥Further Information
为什么 React Context 不是 "状态管理" 工具(以及为什么它不取代 Redux)
¥Why React Context is Not a "State Management" Tool (and Why It Doesn't Replace Redux)
Redux 考古学和设计注意(包含早期设计讨论和项目设计目标描述的链接)
¥Redux Archeology and Design Notes (with links to early design discussions and descriptions of project design goals)