这是用户在 2024-7-7 11:06 为 https://blog.isquaredsoftware.com/2021/01/context-redux-differences/#using-context 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

Blogged Answers: Why React Context is Not a "State Management" Tool (and Why It Doesn't Replace Redux)
博客回答:为什么 React Context 不是“状态管理”工具(以及为什么它不取代 Redux)

This is a post in the Blogged Answers series.

Definitive answers and clarification on the purpose and use cases for Context and Redux
关于 Context 和 Redux 的目的和用例的明确答案和澄清

Introduction 🔗︎ 简介🔗︎

"Context vs Redux" has been one of the most widely debated topics within the React community ever since the current React Context API was released. Sadly, most of this "debate" stems from confusion over the purpose and use cases for these two tools. I've answered various questions about Context and Redux hundreds of times across the internet (including my posts Redux - Not Dead Yet!, React, Redux, and Context Behavior, A (Mostly) Complete Guide to React Rendering Behavior, and When (and when not) to Reach for Redux), yet the confusion continues to get worse.
自从当前的 React Context API 发布以来,“Context 与 Redux”一直是 React 社区中争论最广泛的话题之一。可悲的是,这种“争论”大部分源于对这两种工具的目的和用例的混淆。我已经在互联网上回答了数百次有关 Context 和 Redux 的各种问题(包括我的帖子 Redux - Not Dead Yet!、React、Redux 和 Context Behaviour、A (Mostly) Complete Guide to React Rendering Behaviour 以及 When (and当没有时)到达 Redux),但混乱继续变得更糟。

Given the prevalence of questions on this topic, I'm putting together this post as a definitive answer to those questions. I'll try to clarify what Context and Redux actually are, how they're meant to be used, how they're different, and when you should use them.
鉴于有关此主题的问题普遍存在,我将这篇文章整理为这些问题的明确答案。我将尝试阐明 Context 和 Redux 的实际含义、它们的用途、它们的不同之处以及何时应该使用它们。

TL;DR 🔗︎ 长话短说🔗︎

Are Context and Redux the same thing? 🔗︎
Context 和 Redux 是一回事吗? 🔗︎

No. They are different tools that do different things, and you use them for different purposes.

Is Context a "state management" tool? 🔗︎
Context 是一个“状态管理”工具吗? 🔗︎

No. Context is a form of Dependency Injection. It is a transport mechanism - it doesn't "manage" anything. Any "state management" is done by you and your own code, typically via useState/useReducer.
不。上下文是依赖注入的一种形式。它是一种传输机制——它不“管理”任何东西。任何“状态管理”都是由您和您自己的代码完成,通常通过 useState/useReducer

Are Context and useReducer a replacement for Redux? 🔗︎
Context 和 useReducer 是 Redux 的替代品吗? 🔗︎

No. They have some similarities and overlap, but there are major differences in their capabilities.

When should I use Context? 🔗︎
我什么时候应该使用上下文? 🔗︎

Any time you have some value that you want to make accessible to a portion of your React component tree, without passing that value down as props through each level of components.
任何时候你想要让 React 组件树的一部分访问某些值,而不需要将该值作为 props 通过每个级别的组件传递下去。

When should I use Context and useReducer? 🔗︎
我什么时候应该使用 Context 和 useReducer ? 🔗︎

When you have moderately complex React component state management needs within a specific section of your application.
当您的应用程序的特定部分有中等复杂的 React 组件状态管理需求时。

When should I use Redux instead? 🔗︎
我什么时候应该使用 Redux? 🔗︎

Redux is most useful in cases when:
Redux 在以下情况下最有用:

  • You have larger amounts of application state that are needed in many places in the app
  • The app state is updated frequently over time
  • The logic to update that state may be complex
  • The app has a medium or large-sized codebase, and might be worked on by many people
  • You want to be able to understand when, why, and how the state in your application has updated, and visualize the changes to your state over time
  • You need more powerful capabilities for managing side effects, persistence, and data serialization

Table of Contents 🔗︎

Understanding Context and Redux 🔗︎
理解上下文和 Redux 🔗︎

In order to use any tool correctly, it's critical to understand:

  • What its purpose is
  • What problems it's trying to solve
  • When and why it was originally created

It's also critical to understand what problems you are trying to solve in your own application right now, and pick the tools that solve your problem the best - not because someone else said you should use them, not because they’re popular, but because this is what works best for you in this particular situation.
了解您现在正在尝试在自己的应用程序中解决哪些问题,并选择最能解决您的问题的工具也很重要 - 不是因为别人说您应该使用它们,也不是因为它们很受欢迎,而是因为是在这种特殊情况下最适合您的方法。

Most of the confusion over "Context vs Redux" stems from a lack of understanding about what these tools actually do, and what problems they solve. So, in order to actually know when to use them, we need to first clearly define what they do and what problems they solve.
对“Context 与 Redux”的大部分困惑源于对这些工具的实际用途以及它们解决的问题缺乏了解。因此,为了真正知道何时使用它们,我们需要首先清楚地定义它们的作用以及它们解决的问题。

What is React Context? 🔗︎
什么是 React 上下文? 🔗︎

Let's start by looking at the actual description of Context from the React docs:
让我们首先看看 React 文档中对 Context 的实际描述:

Context provides a way to pass data through the component tree without having to pass props down manually at every level.
Context 提供了一种通过组件树传递数据的方法,而无需在每个级别手动向下传递 props。

In a typical React application, data is passed top-down (parent to child) via props, but this can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree.
在典型的 React 应用程序中,数据通过 props 自上而下(父级到子级)传递,但这对于应用程序中许多组件所需的某些类型的 props(例如区域设置首选项、UI 主题)来说可能很麻烦。 Context 提供了一种在组件之间共享此类值的方法,而无需在树的每个级别显式传递 prop。

Notice that it does not say anything about "managing" values - it only refers to "passing" and "sharing" values.
请注意,它没有提及任何有关“管理”值的内容 - 它仅指“传递”和“共享”值。

The current React Context API (React.createContext()) was first released in React 16.3. It replaced the legacy context API, which had been available since early versions of React, but had major design flaws. The primary problem with legacy context was that updates to values passed down via context could be "blocked" if a component skipped rendering via shouldComponentUpdate. Since many components relied on shouldComponentUpdate for performance optimizations, that made legacy context useless for passing down plain data. createContext() was designed to solve that problem, so that any update to a value will be seen in child components even if a component in the middle skips rendering.
当前的 React Context API ( React.createContext() ) 首次在 React 16.3 中发布。它取代了旧版上下文 API,该 API 自 React 早期版本以来就已可用,但存在重大设计缺陷。遗留上下文的主要问题是,如果组件通过 shouldComponentUpdate 跳过渲染,则通过上下文传递的值的更新可能会被“阻止”。由于许多组件依赖 shouldComponentUpdate 进行性能优化,这使得遗留上下文对于传递纯数据毫无用处。 createContext() 旨在解决该问题,因此即使中间的组件跳过渲染,对值的任何更新都将在子组件中看到。

Using Context 🔗︎

Using React Context in an app requires a few steps:
在应用程序中使用 React Context 需要几个步骤:

  • First, call const MyContext = React.createContext() to create a context object instance
    首先调用 const MyContext = React.createContext() 创建上下文对象实例
  • In a parent component, render <MyContext.Provider value={someValue}>. This puts some single piece of data into the context. That value could be anything - a string, a number, an object, an array, a class instance, an event emitter, and so on.
    在父组件中,渲染 <MyContext.Provider value={someValue}> 。这会将一些单条数据放入上下文中。该值可以是任何内容 - 字符串、数字、对象、数组、类实例、事件发射器等等。
  • Then, in any component nested inside that provider, call const theContextValue = useContext(MyContext).
    然后,在嵌套在该提供程序内的任何组件中,调用 const theContextValue = useContext(MyContext)

Whenever the parent component re-renders and passes in a new reference to the context provider as the value, any component that reads from that context will be forced to re-render.
每当父组件重新渲染并将新引用作为 value 传递给上下文提供者时,从该上下文读取的任何组件都将被迫重新渲染。

Most commonly, the value for a context is something that comes from React component state, along these lines:
最常见的是,上下文的 value 来自 React 组件状态,大致如下:

function ParentComponent() {
  const [counter, setCounter] = useState(0);

  // Create an object containing both the value and the setter
  const contextValue = {counter, setCounter};

  return (
    <MyContext.Provider value={contextValue}>
      <SomeChildComponent />

A child component then can call useContext and read the value:
然后子组件可以调用 useContext 并读取值:

function NestedChildComponent() {
  const { counter, setCounter } = useContext(MyContext);

  // do something with the counter value and setter

Purpose and Use Cases for Context 🔗︎

Based on that, we can see that Context doesn't actually "manage" anything at all. Instead, it's like a pipe or a wormhole. You put something in the top end of the pipe using the <MyContext.Provider>, and that one thing (whatever it is) goes down through the pipe until it pops out the other end where another component asks for it with useContext(MyProvider).
基于此,我们可以看到 Context 实际上根本不“管理”任何东西。相反,它就像管道或虫洞。您使用 <MyContext.Provider> 将一些东西放在管道的顶端,然后该东西(无论它是什么)穿过管道,直到它从另一端弹出,另一个组件用 useContext(MyProvider) 请求它。

So, the primary purpose for using Context is to avoid "prop-drilling". Rather than pass this value down as a prop, explicitly, through every level of the component tree that needs it, any component that's nested inside the <MyContext.Provider> can just say useContext(MyContext) to grab the value as needed. This does simplify the code, because we don't have to write all the extra prop-passing logic.
所以,使用 Context 的主要目的是避免“prop-drilling”。嵌套在 <MyContext.Provider> 中的任何组件都可以直接说 useContext(MyContext) 来根据需要获取该值,而不是将此值作为 prop 明确地传递到需要它的组件树的每个级别。这确实简化了代码,因为我们不必编写所有额外的属性传递逻辑。

Conceptually, this is a form of "Dependency Injection". We know that the child component needs a value of a certain type, but it doesn't try to create or set up that value itself. Instead, it assumes that some parent component will pass down that value, at runtime.

What is Redux? 🔗︎
什么是 Redux? 🔗︎

For comparison, let's look at the description from the "Redux Essentials" tutorial in the Redux docs:
为了进行比较,我们看一下 Redux 文档中“Redux Essentials”教程中的描述:

Redux is a pattern and library for managing and updating application state, using events called "actions". It serves as a centralized store for state that needs to be used across your entire application, with rules ensuring that the state can only be updated in a predictable fashion.
Redux 是一种使用称为“操作”的事件来管理和更新应用程序状态的模式和库。它充当需要在整个应用程序中使用的状态的集中存储,并通过规则确保状态只能以可预测的方式更新。

Redux helps you manage "global" state - state that is needed across many parts of your application.
Redux 帮助您管理“全局”状态 - 应用程序许多部分所需的状态。

The patterns and tools provided by Redux make it easier to understand when, where, why, and how the state in your application is being updated, and how your application logic will behave when those changes occur.
Redux 提供的模式和工具使您可以更轻松地了解应用程序中的状态何时、何地、为何以及如何更新,以及发生这些更改时应用程序逻辑的行为方式。

Note that this description:

  • specifically refers to "managing state"
  • says that the purpose of Redux is to help you understand how state changes over time
    说 Redux 的目的是帮助你理解状态如何随时间变化

Historically, Redux was originally created as an implementation of the "Flux Architecture", which was a pattern first suggested by Facebook in 2014, a year after React came out. Following that announcement, the community created dozens of Flux-inspired libraries with varying approaches to the Flux concepts. Redux came out in 2015, and quickly won the "Flux Wars" because it had the best design, matched the problems people were trying to solve, and worked great with React.
从历史上看,Redux 最初是作为“Flux 架构”的实现而创建的,该模式是 Facebook 于 2014 年(即 React 推出一年后)首次提出的模式。该公告发布后,社区创建了数十个受 Flux 启发的库,并采用不同的方法来实现 Flux 概念。 Redux 于 2015 年问世,并很快赢得了“Flux Wars”,因为它拥有最好的设计,符合人们试图解决的问题,并且与 React 配合得很好。

Architecturally, Redux emphasizes using functional programming principles to help you write as much of your code as possible as predictable "reducer" functions, and separating the idea of "what event happened" from the logic that determines "how the state updates when that event happens". Redux also uses middleware as a way to extend the capabilities of the Redux store, including handling side effects.
在架构上,Redux 强调使用函数式编程原理来帮助您编写尽可能多的代码作为可预测的“reducer”函数,并将“发生了什么事件”的想法与确定“事件发生时状态如何更新”的逻辑分开。 ”。 Redux 还使用中间件作为扩展 Redux 存储功能的一种方式,包括处理副作用。

Redux also has the Redux Devtools, which allow you to see the history of actions and state changes in your app over time.
Redux 还具有 Redux Devtools,它允许您查看应用程序中的操作历史记录和状态随时间的变化。

Redux and React 🔗︎
Redux 和 React 🔗︎

Redux itself is UI-agnostic - you can use it with any UI layer (React, Vue, Angular, vanilla JS, etc), or without any UI at all.
Redux 本身与 UI 无关 - 您可以将它与任何 UI 层(React、Vue、Angular、vanilla JS 等)一起使用,或者根本不使用任何 UI。

That said, Redux is most commonly used with React. The React-Redux library is the official UI binding layer that lets React components interact with a Redux store by reading values from Redux state and dispatching actions. So, when most people refer to "Redux", they actually mean "using a Redux store and the React-Redux library together".
也就是说,Redux 最常与 React 一起使用。 React-Redux 库是官方的 UI 绑定层,它允许 React 组件通过从 Redux 状态读取值并分派操作来与 Redux 存储进行交互。因此,当大多数人提到“Redux”时,他们实际上是指“一起使用 Redux 存储和 React-Redux 库”。

React-Redux allows any React component in the application to talk to the Redux store. This is only possible because React-Redux uses Context internally. However, it's critical to note that React-Redux only passes down the Redux store instance via context, not the current state value!. This is actually an example of using Context for dependency injection, as mentioned above. We know that our Redux-connected React components need to talk to a Redux store, but we don't know or care which Redux store that is when we define the component. The actual Redux store is injected into the tree at runtime using the React-Redux <Provider> component.
React-Redux 允许应用程序中的任何 React 组件与 Redux 存储进行通信。这是可能的,因为 React-Redux 在内部使用了 Context。然而,需要注意的是,React-Redux 仅通过上下文传递 Redux 存储实例,而不是当前状态值!这实际上是一个使用 Context 进行依赖注入的例子,如上所述。我们知道与 Redux 连接的 React 组件需要与 Redux 存储进行通信,但是当我们定义组件时,我们不知道也不关心是哪个 Redux 存储。实际的 Redux 存储在运行时使用 React-Redux <Provider> 组件注入到树中。

Because of this, React-Redux can also be used to avoid prop-drilling, specifically because React-Redux uses Context internally. Instead of explicitly putting a new value into a <MyContext.Provider> yourself, you can put that data into the Redux store and then access it anywhere.
因此,React-Redux 也可以用来避免 prop-drilling,特别是因为 React-Redux 在内部使用 Context。您可以将该数据放入 Redux 存储中,然后在任何地方访问它,而不是自己显式地将新值放入 <MyContext.Provider> 中。

Purposes and Use Cases for (React-)Redux 🔗︎
(React-)Redux 的目的和用例 🔗︎

The primary reason to use Redux is captured in the description from the Redux docs:
Redux 文档的描述中描述了使用 Redux 的主要原因:

The patterns and tools provided by Redux make it easier to understand when, where, why, and how the state in your application is being updated, and how your application logic will behave when those changes occur.
Redux 提供的模式和工具使您可以更轻松地了解应用程序中的状态何时、何地、为何以及如何更新,以及发生这些更改时应用程序逻辑的行为方式。

There are additional reasons why you might want to use Redux. "Avoiding prop-drilling" is one of those other reasons. Many people chose Redux early on specifically to let them avoid prop-drilling, because React's legacy context was broken and React-Redux worked correctly.
您可能想要使用 Redux 还有其他原因。 “避免螺旋钻探”是其他原因之一。许多人很早就选择了 Redux,专门为了让他们避免 prop-drilling,因为 React 的遗留上下文已经被破坏,而 React-Redux 可以正常工作。

Other valid reasons to use Redux include:
使用 Redux 的其他正当理由包括:

  • Wanting to write your state management logic completely separate from the UI layer
    想要编写与 UI 层完全分离的状态管理逻辑
  • Sharing state management logic between different UI layers (such as an application that is being migrated from AngularJS to React)
    在不同 UI 层之间共享状态管理逻辑(例如从 AngularJS 迁移到 React 的应用程序)
  • Using the power of Redux middleware to add additional logic when actions are dispatched
    使用 Redux 中间件的强大功能在分派操作时添加额外的逻辑
  • Being able to persist portions of the Redux state
    能够保留部分 Redux 状态
  • Enabling bug reports that can be replayed by developers
  • Faster debugging of logic and UI while in development
    在开发过程中更快地调试逻辑和 UI

Dan Abramov listed a number of these use cases when he wrote his post You Might Not Need Redux, all the way back in 2016.
Dan Abramov 在 2016 年撰写文章《You Might Not Need Redux》时列出了许多这样的用例。

Why Context is Not "State Management" 🔗︎

"State" is any data that describes the behavior of an application. We could divide that into categories like "server state", "communications state", and "location state" if we want to, but the key point is that there is data being stored, read, updated, and used.

David Khourshid, author of the XState library and an expert on state machines, said:
XState 库的作者、状态机专家 David Khourshid 表示:

"State management is how state changes over time."

Based on that, we can say that "state management" means having ways to:

  • store an initial value 存储一个初始值
  • read the current value 读取当前值
  • update a value 更新一个值

There's also typically a way to be notified when the current value has changed.

React's useState and useReducer hooks are good example of state management. With both of those hooks, you can:
React 的 useStateuseReducer 钩子是状态管理的一个很好的例子。通过这两个钩子,您可以:

  • store an initial value by calling the hook
  • read the current value, also by calling the hook
  • update the value by calling the supplied setState or dispatch function
    通过调用提供的 setStatedispatch 函数来更新值
  • Know that the value has been updated because the component re-rendered

Similarly, Redux and MobX are clearly state management as well:
同样,Redux 和 MobX 显然也是状态管理:

  • Redux stores an initial value by calling the root reducer, lets you read the current value with store.getState(), updates the value with store.dispatch(action), and notifies listeners that the store updated via store.subscribe(listener)
    Redux 通过调用根减速器来存储初始值,让您可以使用 store.getState() 读取当前值,使用 store.dispatch(action) 更新值,并通过 store.subscribe(listener) 通知监听器存储已更新
  • MobX stores an initial value by assigning field values in a store class, lets you read the current value by accessing the store's fields, updates values by assigning to those fields, and notifies that changes happened via autorun() and computed()
    MobX 通过在存储类中分配字段值来存储初始值,允许您通过访问存储的字段来读取当前值,通过分配给这些字段来更新值,并通过 autorun()computed() 通知发生的更改

We can even say that server caching tools like React-Query, SWR, Apollo, and Urql fit the definition of "state management" - they store initial values based on the fetched data, return the current value via their hooks, allow updates via "server mutations", and notify of changes via re-rendering the component.
我们甚至可以说像 React-Query、SWR、Apollo 和 Urql 这样的服务器缓存工具符合“状态管理”的定义 - 它们根据获取的数据存储初始值,通过钩子返回当前值,允许通过“状态管理”进行更新服务器突变”,并通过重新渲染组件来通知更改。

React Context does not meet those criteria. Therefore, Context is not a "state management" tool!
React Context 不符合这些标准。因此,Context并不是一个“状态管理”工具!

As we established earlier, Context does not "store" anything itself. The parent component that renders a <MyContext.Provider> is responsible for deciding what value is passed into the context, and that value typically is based on React component state. The actual "state management" is happening with the useState/useReducer hook.
正如我们之前所确定的,Context 本身并不“存储”任何内容。渲染 <MyContext.Provider> 的父组件负责决定将什么值传递到上下文中,该值通常基于 React 组件状态。实际的“状态管理”是通过 useState/useReducer 挂钩进行的。

As David Khourshid also said:
正如大卫·库尔希德(David Khourshid)所说:

Context is how state (that exists somewhere already) is shared with other components.

Context has little to do with state management.

Or, as a recent tweet put it:

I guess Context is more like hidden props than abstracted state.
我认为 Context 更像是隐藏的 props,而不是抽象的状态。

Think of it this way. We could have written the exact same useState/useReducer code, but prop-drilled the data and the update function down through the component tree. The actual behavior of the app would have been the same overall. All Context does for us is let us skip the prop-drilling.
这样想吧。我们可以编写完全相同的 useState/useReducer 代码,但是通过组件树向下钻取数据和更新函数。应用程序的实际行为总体上是相同的。 Context 为我们所做的一切就是让我们跳过 prop-drilling。

Comparing Context and Redux 🔗︎
比较 Context 和 Redux 🔗︎

Let's review what capabilities Context and React+Redux actually have:
我们来回顾一下 Context 和 React+Redux 到底有哪些能力:

  • Context 语境
    • Does not store or "manage" anything
    • Only works in React components
      仅适用于 React 组件
    • Passes down a single value, which could be anything (primitive, objects, classes, etc)
    • Allows reading that single value
    • Can be used to avoid prop-drilling
    • Does show the current context value for both Provider and Consumer components in the React DevTools, but does not show any history of how that value changed over time
      确实显示 React DevTools 中 ProviderConsumer 组件的当前上下文值,但不显示该值随时间变化的任何历史记录
    • Updates consuming components when the context value changes, but with no way to skip updates
    • Does not include any mechanism for side effects - it's purely for rendering components
      不包含任何副作用机制 - 它纯粹用于渲染组件
  • React+Redux 反应+Redux
    • Stores and manages a single value (which is typically an object)
    • Works with any UI, including outside of React components
      适用于任何 UI,包括 React 组件之外
    • Allows reading that single value
    • Can be used to avoid prop-drilling
    • Can update the value via dispatching an action and running reducers
    • Has DevTools that show the history of all dispatched actions and state changes over time
    • Uses middleware to allow app code to trigger side effects
    • Allows components to subscribe to store updates, extract specific pieces of the store state, and only re-render when those values change

So, clearly these are very different tools with different capabilities. The only overlap between them, really, is "can be used to avoid prop-drilling".

Context and useReducer 🔗︎
上下文和 useReducer 🔗︎

One problem with the "Context vs Redux" discussions is that people often actually mean "I'm using useReducer to manage my state, and Context to pass down that value". But, they never state that explicitly - they just say "I'm using Context". That's a common cause of the confusion I see, and it's really unfortunate because it helps perpetuate the idea that Context "manages state".
“Context 与 Redux”讨论的一个问题是,人们实际上的意思通常是“我正在使用 useReducer 来管理我的状态,并使用 Context 来传递该值”。但是,他们从来没有明确说明这一点 - 他们只是说“我正在使用 Context”。这是我所看到的造成混乱的常见原因,这确实很不幸,因为它有助于延续上下文“管理状态”的想法。

So, let's talk about the Context + useReducer combination specifically. Yes, Context + useReducer does look an awful lot like Redux + React-Redux. They both have:
那么,我们来具体说说Context + useReducer 组合。是的,Context + useReducer 确实看起来非常像 Redux + React-Redux。他们都有:

  • A stored value 储值
  • A reducer function 减速器功能
  • dispatching of actions 动作的调度
  • a way to pass down that value and read it in nested components

However, there's still a number of very significant differences in the capabilities and behaviors of Context + useReducer vs those of Redux + React-Redux. I covered the key points in my posts React, Redux, and Context Behavior and A (Mostly) Complete Guide to React Rendering Behavior. Summarizing here:
然而,Context + useReducer 与 Redux + React-Redux 的功能和行为仍然存在许多非常显着的差异。我在文章《React、Redux 和上下文行为》和《React 渲染行为(大部分)完整指南》中介绍了要点。在此总结一下:

  • Context + useReducer relies on passing the current state value via Context. React-Redux passes the current Redux store instance via Context.
    Context + useReducer 依赖于通过 Context 传递当前状态值。 React-Redux 通过 Context 传递当前的 Redux 存储实例。

  • That means that when useReducer produces a new state value, all components that are subscribed to that context will be forced to re-render, even if they only care about part of the data. This may lead to performances issues, depending on the size of the state value, how many components are subscribed to that data, and how often they re-render. With React-Redux, components can subscribe to specific pieces of the store state, and only re-render when those values change.
    这意味着当 useReducer 生成新的状态值时,订阅该上下文的所有组件都将被迫重新渲染,即使它们只关心部分数据。这可能会导致性能问题,具体取决于状态值的大小、订阅该数据的组件数量以及重新渲染的频率。使用 React-Redux,组件可以订阅存储状态的特定部分,并且仅在这些值发生变化时重新渲染。

In addition, there's some other important differences as well:

  • Context + useReducer are React features, and therefore cannot be used outside of React. A Redux store is independent of any UI, and so it can be used separate from React.
    Context + useReducer 是 React 特性,因此不能在 React 之外使用。 Redux 存储独立于任何 UI,因此它可以与 React 分开使用。
  • The React DevTools allow viewing the current context value, but not any of the historical values or changes over time. The Redux DevTools allow seeing all actions that were dispatched, the contents of each action, the state as it existed after each action was processed, and the diffs between each state over time.
    React DevTools 允许查看当前上下文值,但不允许查看任何历史值或随时间的变化。 Redux DevTools 允许查看已分派的所有操作、每个操作的内容、处理每个操作后存在的状态以及每个状态之间随时间变化的差异。
  • useReducer does not have middleware. You can do some side-effect-y things with useEffect in combination with useReducer, and I've even seen some attempts to wrap useReducer with something that resembles a middleware, but both of those are severely limited in comparison to the functionality and capabilities of Redux middleware.
    useReducer 没有中间件。您可以将 useEffectuseReducer 结合起来做一些副作用,我什至看到一些尝试用类似于中间件的东西包装 useReducer ,但相比之下,这两种方法都受到严重限制Redux 中间件的功能和能力。

It's worth repeating what Sebastian Markbage (React core team architect) said about the uses for Context:
值得重复一下 Sebastian Markbage(React 核心团队架构师)关于 Context 用途的说法:

My personal summary is that new context is ready to be used for low frequency unlikely updates (like locale/theme). It's also good to use it in the same way as old context was used. I.e. for static values and then propagate updates through subscriptions. It's not ready to be used as a replacement for all Flux-like state propagation.
我个人的总结是,新的上下文已准备好用于低频不太可能的更新(例如区域设置/主题)。以与使用旧上下文相同的方式使用它也很好。 IE。获取静态值,然后通过订阅传播更新。它还没有准备好用作所有类似 Flux 状态传播的替代品。

There's a lot of posts out there that recommend setting up multiple separate contexts for different chunks of state, both to cut down on unnecessary re-renders and to scope concerns. Some of those also suggest adding your own "context selector components", which require a mixture of React.memo(), useMemo(), and carefully splitting things up so there's two separate contexts for each segment of state (one for the data, and one for the updater functions). Sure, it's possible to write code that way, but at that point you're just reinventing React-Redux, poorly.
有很多帖子建议为不同的状态块设置多个单独的上下文,以减少不必要的重新渲染并缩小关注范围。其中一些还建议添加您自己的“上下文选择器组件”,这需要 React.memo()useMemo() 的混合,并仔细地将内容分开,以便每个状态段都有两个单独的上下文(一个用于数据,一个用于数据)用于更新程序功能)。当然,可以用这种方式编写代码,但那时你只是在重新发明 React-Redux,很糟糕。

So, even though Context + useReducer sorta-resemble Redux + React-Redux at a quick glance... they are not fully equivalent and cannot truly replace Redux!
因此,尽管 Context + useReducer 乍一看有点像 Redux + React-Redux...但它们并不完全等效,不能真正取代 Redux!

Choosing the Right Tool 🔗︎

As I said earlier, it's critical to understand what problems a tool solves, and know what problems you have, in order to correctly choose the right tool to solve your problems.

Use Case Summary 🔗︎

Let's recap the use cases for each of these:

  • Context:  语境:
    • Passing down a value to nested components without prop-drilling
      将值传递给嵌套组件而无需进行 prop-drilling
  • useReducer
    • Moderately complex React component state management using a reducer function
  • Context + useReducer:
    上下文+ useReducer
    • Moderately complex React component state management using a reducer function, and passing that state value down to nested components without prop-drilling
  • Redux 终极版
    • Moderate to highly complex state management using reducer functions
    • Traceability for when, why, and how state changed over time
    • Wanting to write your state management logic completely separate from the UI layer
      想要编写与 UI 层完全分离的状态管理逻辑
    • Sharing state management logic between different UI layers
      在不同 UI 层之间共享状态管理逻辑
    • Using the power of Redux middleware to add additional logic when actions are dispatched
      使用 Redux 中间件的强大功能在分派操作时添加额外的逻辑
    • Being able to persist portions of the Redux state
      能够保留部分 Redux 状态
    • Enabling bug reports that can be replayed by developers
    • Faster debugging of logic and UI while in development
      在开发过程中更快地调试逻辑和 UI
  • Redux + React-Redux
    • All of the use cases for Redux, plus interacting with the Redux store in your React components
      Redux 的所有用例,以及与 React 组件中的 Redux 存储交互

Again, these are different tools that solve different problems!

Recommendations 🔗︎ 推荐🔗︎

So, how do you decide whether to use Context, Context + useReducer, or Redux + React-Redux?
那么,如何决定是使用 Context、Context + useReducer 还是 Redux + React-Redux?

You need to determine which of these tools best matches the set of problems that you're trying to solve!

  • If the only thing you need to do is avoid prop-drilling, then use Context
    如果您唯一需要做的就是避免 prop-drilling,那么使用 Context
  • If you've got some moderately complex React component state, or just really don't want to use an external library, go with Context + useReducer
    如果您有一些中等复杂的 React 组件状态,或者只是真的不想使用外部库,请使用 Context + useReducer
  • If you want better traceability of the changes to your state over time, need to ensure that only specific components re-render when the state changes, need more powerful capabilities for managing side effects, or have other similar problems, use Redux + React-Redux
    如果您希望更好地跟踪状态随时间的变化,需要确保只有特定组件在状态变化时重新渲染,需要更强大的功能来管理副作用,或者有其他类似的问题,请使用 Redux + React-Redux

My personal opinion is that if you get past 2-3 state-related contexts in an application, you're re-inventing a weaker version of React-Redux and should just switch to using Redux.
我个人的观点是,如果您在应用程序中遇到了 2-3 个与状态相关的上下文,那么您正在重新发明 React-Redux 的较弱版本,应该切换到使用 Redux。

Another common concern is that "using Redux means too much 'boilerplate'". Those complaints are very outdated, as "modern Redux" is significantly easier to learn and use than what you may have seen before. Our official Redux Toolkit package eliminates those "boilerplate" concerns, and the React-Redux hooks API simplifies using Redux in your React components. As one user recently told me:
另一个常见的担忧是“使用 Redux 意味着太多的‘样板’”。这些抱怨已经过时了,因为“现代 Redux”比您以前见过的更容易学习和使用。我们的官方 Redux Toolkit 包消除了这些“样板”问题,React-Redux hooks API 简化了在 React 组件中使用 Redux。正如一位用户最近告诉我的:

We just switched from context and hooks over to RTK on one of our production application's frontends. That thing processes a little over $1B/year. Fantastic stuff in the toolkit. The RTK is the polish that helped me convince the rest of the teams to buy into the refactor. I also did a boilerplate analysis for that refactor and it's actually LESS boilerplate to use the RTK than it is to use the recommended dispatch pattern in contexts. Not to mention how much easier it is to process data.
我们刚刚在生产应用程序的前端之一上从上下文切换到 RTK。那东西每年处理的费用略高于 1B 美元。工具包中的东西很棒。 RTK 是帮助我说服其他团队接受重构的改进。我还对该重构进行了样板分析,实际上使用 RTK 的样板比在上下文中使用推荐的调度模式要少。更不用说处理数据是多么容易了。

Yes, adding RTK and React-Redux as dependencies does add additional byte size to your application bundle over just Context + useReducer, because those are built in to React. But, the tradeoffs are worth it - better state traceability, simpler and more predictable logic, and improved component rendering performance.
是的,添加 RTK 和 React-Redux 作为依赖项确实会比 Context + useReducer 添加额外的字节大小到您的应用程序包中,因为它们内置于 React 中。但是,这种权衡是值得的 - 更好的状态可追溯性、更简单且更可预测的逻辑以及改进的组件渲染性能。

It's also important to point out that these are not mutually exclusive options - you can use Redux, Context, and useReducer together at the same time! We specifically encourage putting "global state" in Redux and "local state" in React components, and carefully deciding whether each piece of state should live in Redux or component state. So, you can use Redux for some state that's global, and useReducer + Context for some state that's more local, and Context by itself for some semi-static values, all at the same time in the same application.
同样重要的是要指出,这些并不是相互排斥的选项 - 您可以同时使用 Redux、Context 和 useReducer !我们特别鼓励将“全局状态”放在 Redux 中,将“本地状态”放在 React 组件中,并仔细决定每个状态应该存在于 Redux 还是组件状态中。因此,您可以使用 Redux 来处理某些全局状态,使用 useReducer + Context 来处理某些更本地化的状态,并使用 Context 本身来处理一些半静态值,所有这些都在同一个应用程序中同时进行。

To be clear, I'm not saying that all apps should use Redux, or that Redux is always a better choice! There's many nuances to this discussion. I am saying that Redux is a valid choice, there are many reasons to choose Redux, and the tradeoffs for choosing Redux are a net win more often than many people think.
需要明确的是,我并不是说所有应用程序都应该使用 Redux,或者 Redux 始终是更好的选择!这次讨论有很多细微差别。我是说 Redux 是一个有效的选择,选择 Redux 的理由有很多,而且选择 Redux 的权衡往往比许多人想象的更有利。

And finally, Context and Redux are not the only tools to think about. There's many other tools out there that solve other aspects of state management in different ways. MobX is another widely used option that uses OOP and observables to automatically update data dependencies. Jotai, Recoil, and Zustand offer lighter-weight state update approaches. Data fetching libraries like React Query, SWR, Apollo, and Urql all provide abstractions that simplify common patterns for working with cached server state (and the upcoming "RTK Query" library will do the same for Redux Toolkit). Again, these are different tools, with different purposes and use cases, and are worth evaluating based on your use case.
最后,Context 和 Redux 并不是唯一需要考虑的工具。还有许多其他工具可以以不同的方式解决状态管理的其他方面。 MobX 是另一个广泛使用的选项,它使用 OOP 和可观察量来自动更新数据依赖项。 Jotai、Recoil 和 Zustand 提供了更轻量级的状态更新方法。像 React Query、SWR、Apollo 和 Urql 这样的数据获取库都提供了抽象,可以简化处理缓存服务器状态的常见模式(即将推出的“RTK Query”库将为 Redux Toolkit 做同样的事情)。同样,这些是不同的工具,具有不同的目的和用例,并且值得根据您的用例进行评估。

Final Thoughts 🔗︎

I realize that this post won't stop the seemingly never-ending debate over "Context vs Redux?!?!?!?!?". There's too many people out there, too many conflicting ideas, and too much miscommunication and misinformation.
我意识到这篇文章不会停止关于“Context vs Redux?!?!?!?!?”的看似永无休止的争论。那里有太多的人、太多相互冲突的想法、太多的沟通不畅和错误信息。

Having said that, I hope that this post has clarified what these tools actually do, how they're different, and when you should actually consider using them. (And maybe, just maybe, some folks will read this article and not feel the need to post the same question that's been asked a million times already...)
话虽如此,我希望这篇文章能够阐明这些工具的实际用途、它们的不同之处以及您何时应该真正考虑使用它们。 (也许,只是也许,有些人会阅读这篇文章,而不觉得有必要发布已经被问过一百万次的相同问题......)

Further Information 🔗︎

This is a post in the Blogged Answers series. Other posts in this series:

Drag to outliner or Upload