redux 是否会在对商店进行任何更新时评估商店的所有听众?

Does redux evaluate all listeners to the store on any update to the store?

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

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

many individual components should be connected to the store instead of just a few... [docs]

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

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

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

例如给定以下示例 listener

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 挂钩附加一个侦听器时,我想知道我是否应该尽量减少将另一个侦听器绑定到商店。

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

也许这是对州立图书馆的过早优化...但如果是这样,为什么呢?是否假设使用 redux 的应用程序将具有简单快速的选择器,并且应用程序瓶颈将在其他地方?

From what I can tell, redux will notify all subscribers to the store when anything in the store changes no matter if it's a subscription to a deeply nested leaf or a subscription to the top level of the state.

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

之间的区别

You could end up with lots of listeners and potentially performance issues?

使用 React-Redux,您可以通过选择器 (useSelector/connect).

默认情况下,React-Redux 中的每个订阅组件都将 重新渲染 如果其订阅的存储部分发生变化,您通过一个用于渲染的选择器。

但是对于 Redux:

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

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

Premature optimization is spending a lot of time on something that you may not actually need. “Premature optimization is the root of all evil” is a famous saying among software developers.

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

所有订阅的 组件 将仅在状态部分发生更改(使用选择器增强)时重新呈现 - 影响 UI,因此考虑优化,您应该订阅商店的可比部分

我假设您正在谈论使用 react-redux 从 redux(您问题中的标签)获取状态的反应组件。缺少 react-redux 标签,但这是 standard create react app template.

中最常使用的标签

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

如果一个动作导致创建一个新状态,那么所有传递给 useSelector 或 mapStateToProps 的函数都将被执行,只有当它们 return 一个值与以前的价值。对于 mapStateToProps,它的工作方式略有不同,因为它与 returned.

值进行 shallow equal 比较

您可以使用 reselect 组合选择器并重新使用逻辑来获取某些分支 and/or 调整来自状态的 returned 数据并记住调整后的数据,因此 jsx 是没有不必要的创建。

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

你能做的最糟糕的事情是传递一个在每次渲染时都重新创建的处理函数,因为这将导致 jsx 因 props 更改而重新创建,并且 DOM 由于处理函数导致重新创建虚拟 DOM 比较失败,例如:

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

您可以使用 React 中的 useCallback 钩子来防止这种情况发生,或者创建一个 PersonContainer,它只会在重新呈现时创建回调 () => someAction(person.id)

我是 Redux 维护者和 React-Redux v7 的作者。其他几个答案其实都很好,但我想提供一些额外的信息。

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

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

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

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

如需了解更多信息,您应该阅读我关于 React 和 React-Redux 如何工作的帖子,这些主题非常详细:

您可能还想阅读 the Github issue that discussed the development of React-Redux v7,我们放弃了在 v6 中使用 React Context 进行状态传播的尝试,因为它的性能不够好,并返回到在组件中使用直接存储订阅v7.

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

这不是原始问题的确切答案,但相关性足以被提及。

有时在非常复杂的 React 系统上,Redux 通知实际上可能会造成麻烦。如果所有其他优化技术似乎都已用尽,您可能想对 Redux 本身进行一些修改。

参见: https://www.npmjs.com/search?q=redux-notification-enhancer

此增强器允许通过减少来自 Redux 的通知来减少 React 中的渲染量。它可以通过启用节流 and/or 将某些操作标记为被动(更新状态但不导致渲染)来实现。这两种优化技术都会增加复杂性和性能 - 请谨慎使用。