11

据我所知,当 store 中的任何内容发生变化时,redux 将通知 store 的所有订阅者,无论是对深度嵌套的叶子的订阅还是对状态顶层的订阅。

在遵循指导原则的应用程序中:

许多单独的组件应该连接到商店,而不是只有几个...... [文档]

您最终可能会遇到很多听众和潜在的性能问题?

免责声明:我了解,如果选择器功能的结果发生变化,选择器功能只会导致重新渲染。我知道仅仅因为评估了侦听器函数,并不意味着订阅组件将重新呈现。我知道评估选择器功能对于反应组件渲染来说相对便宜。

但是,我只想确认这确实是 redux 的工作原理?

例如,给出以下示例监听器

const result = useSelector(state => state.a.b.c.d.e.f.g.h.i.j.k)

如果我们沿其他路径更新其他值,与上述侦听器无关,例如

const exampleReducer = (state) => {
   return { ...state, asdf: 'asdf' }
}

据我了解,将调用所有侦听器,包括上面的示例。

对于上下文,我的实际用例是我使用的是基于 redux 的https://easy-peasy.now.sh/ 。需要明确的是,我目前在生产中没有任何与绑定太多侦听器相关的性能问题。但是,每次我通过useStoreState钩子附加一个侦听器时,我都想知道是否应该最小化将另一个侦听器绑定到商店。

另外,如果你很好奇,受这种想法的启发,我实现了一个状态树,它只通知相关的 listeners

也许这对于状态库来说是一个过早的优化......但如果是这样,为什么?是否假设使用 redux 的应用程序将具有简单快速的选择器,并且应用程序瓶颈将在其他地方?

4

3 回答 3

8

我是 Redux 的维护者和 React-Redux v7 的作者。其他几个答案实际上相当不错,但我想提供一些额外的信息。

是的,Redux 存储将始终store.subscribe()在每个分派的操作之后运行所有侦听器回调。然而,这并不意味着所有的 React 组件都会重新渲染。根据您的useSelector(state => state.a.b.c.d)示例,useSelector会将当前选择器结果与之前的选择器结果进行比较,并且仅在值已更改时才强制此组件重新渲染。

我们建议“连接更多组件以从 Redux 读取”的原因有多种:

  • 每个组件将从 Redux 存储中读取较小范围的值,因为它关心的整体数据较少
  • 这意味着在给定操作后将强制重新渲染的组件更少,因为该特定数据实际上被更新的机会更小
  • 相反,这意味着您不会遇到这样的情况:一个大型父组件一次读取多个值,总是被迫重新渲染,因此总是导致其所有组件也重新渲染。

所以,真正的问题不是监听器回调的数量——而是这些监听器回调做了多少工作,以及有多少 React 组件因此被迫重新渲染。总体而言,我们的性能测试表明,让更多的侦听器读取更少的单个数据会导致更少的 React 组件被重新渲染,并且更多的侦听器的成本明显低于更多 React 组件的渲染成本。

有关更多信息,您应该阅读我关于 React 和 React-Redux 如何工作的文章,这些主题非常详细:

您可能还想阅读讨论 React-Redux v7 开发的 Github 问题,我们放弃了在 v6 中使用 React Context 进行状态传播的尝试,因为它的性能不够好,并返回到使用直接存储订阅v7 中的组件。

但是,是的,您提前过多地担心性能。React 和 React-Redux 的行为方式有很多细微差别。您实际上应该在生产环境中对自己的应用程序进行基准测试,以查看您是否确实存在任何有意义的性能问题,然后酌情对其进行优化。

于 2021-01-20T14:46:57.577 回答
4

据我所知,当 store 中的任何内容发生变化时,redux 将通知 store 的所有订阅者,无论是对深度嵌套的叶子的订阅还是对状态顶层的订阅。

是的,所有订阅者都会收到通知。但是请注意Redux和它的React-Redux utils之间的区别。

您最终可能会遇到很多听众和潜在的性能问题?

使用React-Redux ,您可以通过选择器 ( / )订阅 ( Redux的) 商店。useSelectorconnect

默认情况下,如果React-Redux中的每个订阅组件的订阅存储部分发生更改,则将重新渲染它,要处理它,您传递一个选择器来保护渲染。

但是对于Redux

  • 通知本身由 Redux(React 外部)处理。
  • Redux 不处理“深度嵌套的叶子”(React Context API 将其作为 React-Redux 实现的一部分处理)它不处理位置 - 它只是调用回调。
  • 通知在 React 上下文之外的while 循环中进行批处理(优化)。
// There is a single Subscription instance per store
// Code inside Subscription.js
notify() {
  batch(() => {
    let listener = first
    while (listener) {
      listener.callback()
      listener = listener.next
    }
  })
}

总之,如果不是过早优化的情况:

过早的优化会花费大量时间在您可能实际上不需要的东西上。“过早的优化是万恶之源”是软件开发人员的一句名言。

Redux中的所有订阅者都会收到通知,但不会影响 UI。

仅当状态部分发生更改(通过选择器增强)时,所有订阅的组件才会重新呈现 - 影响 UI,因此考虑优化,您应该订阅 store 的可比较部分

于 2021-01-20T10:50:30.177 回答
1

我假设您正在谈论使用 react-redux 从 redux(您问题中的标签)获取状态的反应组件。缺少 react-redux 标记,但这是标准创建反应应用程序模板中最常用和使用的标记。

您可以将useSelector挂钩或mapStateToProps与连接一起使用。两者或多或少都以相同的方式工作。

如果某个操作导致创建新状态,则传递给 useSelector 或 mapStateToProps 的所有函数都将被执行,并且仅当它们返回的值与先前值的引用不同时,才会重新呈现组件。对于 mapStateToProps,它的工作方式略有不同,因为它与返回的值进行了浅层的相等比较。

您可以使用重新选择来组合选择器并重新使用逻辑来获取某些分支和/或调整从状态返回的数据并记住调整后的数据,这样就不会不必要地创建 jsx。

请注意,当组件重新创建 jsx 时,这并不意味着重新创建 DOM,因为 React 将对当前 jsx 与最后一个 jsx 进行虚拟 DOM 比较,但您可以通过使用记忆选择器完全不重新创建 jsx 来优化(使用 reselect ) 并具有纯成分。

您可以做的最糟糕的事情是传递一个在每次渲染时重新创建的处理程序函数,因为这将导致重新创建 jsx,因为道具更改并重新创建 DOM,因为处理程序函数导致虚拟 DOM 比较失败,例如这个:

{people.map((person) => (
  <Person
    key={person.id}
    onClick={() => someAction(person.id)}
    person={person}
  />
))}

你可以使用 React 中的 useCallback 钩子来防止这种情况发生,或者创建一个 PersonContainer 来() => someAction(person.id)仅在重新渲染时创建回调。

于 2021-01-20T11:01:51.977 回答