React Context 性能和建议

Unk*_*per 12 reactjs react-context

您可以在许多网站上找到这个短语,并且它被认为(?)有效:

React Context 通常用于避免 prop 钻探,但众所周知,存在性能问题。当上下文值更改时,所有使用的组件都useContext将重新渲染。

而且:

React 团队提出了一个useSelectedContext钩子来防止大规模 Context 的性能问题。有一个社区库:use-context-selector

然而,对于我来说,上述内容没有任何意义。难道我们不想重新渲染所有使用的组件吗useContext?绝对地!一旦上下文值发生变化,所有使用它的组件都必须重新渲染。否则,UI 将不会与状态同步。那么,性能问题到底是什么

我们可以讨论如何不重新渲染不使用的 Context Provider 的其他子组件useContext,这是可以实现的(react docs):

<TasksContext.Provider value={tasks}>
  <TasksDispatchContext.Provider value={dispatch}>
    {children}
  </TasksDispatchContext.Provider>
</TasksContext.Provider>
Run Code Online (Sandbox Code Playgroud)

通过使用上面的模式,我们可以避免重新渲染所有不使用useContext.

回顾一下:在我看来,正确使用 Context 时不存在性能问题。唯一会重新渲染的组件是那些应该重新渲染的组件。然而,几乎所有参考文献都坚持存在潜在的性能问题,并强调这是 Context 的警告之一。我错过了什么吗?

Ilê*_*ian 12

注意:我将回答您的问题,并根据我在互联网上收集到的信息和个人经验给出最终评论。

TL;DR:性能问题不断提醒您,当您使用时,Context API即使没有显式编写它,您实际上也会将这些 props 传递给使用 Context State 的组件,并且在访问该状态的每个组件的每个状态更改时,这些组件将被重新渲染

回答您帖子中的每个问题:

问题1:我们不想重新渲染所有使用的组件吗useContext

是的,正是如此,正如您所说。

问题2:那么,性能问题到底是什么

prop 发生变化时,组件会重新渲染。使用 时Context API,即使没有显式地将 Context 状态作为 prop 传递,每个状态更改都会触发该组件以及依赖于或接收该状态作为 prop 的子组件的重新渲染。您可以在本文档中阅读此内容,例如:

Context 提供了一种在组件之间共享此类值的方法,而无需在树的每个级别显式传递 prop

这不完全是一个问题,因为文档建议您使用它来存储不会改变太多的全局状态,例如:

  • 主题
  • 认证/登录状态
  • 语言/i18n

这些数据类型:

  • 如果更改,应该触发“全局”重新渲染,因为它会影响整个应用程序
  • 每次应用程序交互时不要改变太多(或根本不改变)
  • 将用于不同的嵌套级别

问题3:我错过了什么吗?

好吧,正如您已经假设的那样,有很多情况与不会发生太大变化的“全局状态”的使用类型不匹配。并且,对于这些,还有一些其他选项可用于处理Context API解决的同一组案例,但代码开销要大得多。其中之一是Redux,它没有这种开销,最明显的原因是:从您的应用程序Redux创建一个并行store,并且不会将值作为 props 传递给每个组件。另一方面,最明显的开销之一是项目应该容纳该库的工具。

Redux但为什么人们首先开始使用(和其他库)呢?

在过去的版本中处理全局状态React是一件事。你可以用很多不同的方式、观点和方法来解决这个问题。迟早,人们开始创建和使用工具和库,通过出于特定或个人原因被认为“更好”的方法来处理该问题。

后来,这些工具/库开始变得更加复杂,并且到处都有更多可能的“连接器”或“中间件”。举个例子,可以添加来Redux处理请求的一个工具是名为 的库,它允许在操作内执行请求(如果我没记错的话),打破了仅将操作编写为来自 的纯函数Redux Thunk的概念。如今,随着/的增长,与请求相关的状态也开始作为并行的“全局状态”进行处理,即使具有缓存和更多功能,也不再使用 / ,从而解决了“全局状态” ”来自请求。ReduxReact QueryTanStack QueryRedux ThunkRedux

在发布Context API稳定和改进的版本后,人们开始将它用于很多项目并作为全局状态管理器。迟早每个人都会开始注意到与太多重新渲染相关的性能问题,这是由于每次各处都有多个道具发生变化而导致的。其中一些只是回到了Redux其他库,但对于其他库来说,事实证明这是非常好的,实用的,涉及更少的开销,并且如果按预期使用则Context API嵌入。React不必处理性能问题的要求与之前描述的相同:

  • 一个不那么频繁变化的全球状态
  • 将用于不同的嵌套级别

Context API如果您不嵌套太多组件,还有一些其他选项可以顺利工作。Route举个例子:如果您在级别而不是级别创建上下文,则为多页表单App。正如您还指出的:

在我看来,正确使用 Context 不存在性能问题

但你可以说,几乎所有工具的使用都脱离了其最初的使用概念。


编辑:在 OP 指出并阅读官方文档中的 Context API 之后,道具不会传递给每个 child,而只是传递给那些使用 context 的人。结果是:对于那些将这些 props 传递给它们的组件的每个子组件。

并且,回答为什么Context API会有性能问题的问题,我计划创建一个存储库来重现和理解,但我的赌注Context是:这可能与每个被称为“组件”而 React 自行处理这一事实有关作为示例,创建“并行结构”,例如Redux/ 。Jotai


hac*_*ape 6

\n

回顾一下:在我看来,正确使用 Context 时不存在性能问题。唯一会重新渲染的组件是那些应该重新渲染的组件。

\n
\n

如果您的提供者仅提供一个简单的值,则您的回顾是正确的。

\n

但实际上,Provider 经常提供一个包含许多分支和叶子的大树对象。然而,每个消费者可能只需要其中的一小部分,甚至是该树上的单个叶子值。

\n

在这种情况下,存在性能问题,因为 Context API 是一个整体销售解决方案。即使更新单个叶子值,仍然需要更新树根对象\xe2\x80\x99s 引用才能发出更改信号。但这反过来又通知每一个消费者useContext

\n

现在,\xe2\x80\x99 是你\xe2\x80\x99 缺少的一点:

\n

确实,每个人都应该收到更改通知,但所有的人都应该重新渲染,这并不正确。最好只有那些依赖于更新的叶子值的消费者才应该重新渲染。

\n

在当前状态下,Context API 并没有\xe2\x80\x99 对此问题提供任何细粒度的控制,因此诸如use-context-selector将选择器模式重新带回我们的视线之类的事情。

\n
\n

从根本上来说,这是一个发布-订阅模型,如果您没有一种机制来允许订阅者决定收听哪个频道,那么您唯一能做的就是向所有订阅者广播所有内容。\xe2\x80\x99 就像叫醒附近的每个人只是为了告诉他们\xe2\x80\x9cAlice 收到了一封新邮件\xe2\x80\x9d,这显然不是最佳选择。

\n

准系统 Redux 设置中也存在同样的问题。这就是为什么选择器模式曾经react-redux非常流行。

\n