性能-Backbone说,React-Redux应用程序真的可以扩展吗? 即使重新选择。 在手机上

在Redux中,对存储的每次更改都会在所有连接的组件上触发notify。 这使开发人员的工作变得非常简单,但是如果您的应用程序具有N个连接的组件,并且N非常大,该怎么办?

商店的每次更改(即使与组件无关)仍会在商店的2702477354374530030050ed路径上运行notify,并进行简单的_.debounce测试。 很快,对吧? 当然,也许一次。 但是N次,每次改变吗? 设计上的这一根本性变化使我质疑Redux的真正可扩展性。

作为进一步的优化,可以使用_.debounce分批处理所有notify调用。即使如此,对每个商店更改进行N ===测试并处理其他逻辑(例如视图逻辑),似乎是一种解决方法。

我正在开发一个拥有数百万用户的健康与健身社交移动网络混合应用程序,并且正在从Backbone过渡到Redux。 在此应用程序中,向用户显示了可滑动界面,该界面使他们可以在类似于Snapchat的不同视图堆栈之间导航,但每个堆栈都具有无限深度。 在最流行的视图类型中,无限滚动器可以高效地处理提要项(如帖子)的加载,渲染,附加和分离。 对于一个敬业的用户来说,滚动浏览成百上千的帖子,然后输入一个用户的供稿,然后输入另一个用户的供稿,等等并不罕见。即使进行了大量的优化,连接的组件也会变得非常庞大。

现在,另一方面,Backbone的设计允许每个视图精确地听取影响它的模型,从而将N减小为常数。

我是否缺少某些东西,还是Redux在大型应用程序方面存在根本缺陷?

Garrett asked 2020-01-14T16:05:32Z
2个解决方案
87 votes

这不是Redux IMHO固有的问题。

顺便说一句,与其尝试同时渲染10万个组件,不如尝试使用诸如react-infinite或类似名称的库来伪造它,而仅渲染列表中可见(或接近)的项目。 即使您成功渲染并更新了100k列表,它仍然无法执行,并且会占用大量内存。 这是一些LinkedIn建议

该解决方案将考虑您仍然尝试在DOM中呈现100k可更新项,并且您不希望在每次更改时都调用100k侦听器(list.push(value))。


2所学校

以实用的方式开发UI应用程序时,基本上有两种选择:

始终从最顶层渲染

它运作良好,但涉及更多样板。 这不是完全建议的Redux方式,但是可以实现,但有一些缺点。 请注意,即使设法建立一个单一的Redux连接,您仍然必须在许多地方拨打很多list.push(value)。 如果您具有无限的视图堆栈(如递归),则还必须将所有中间视图也渲染为虚拟dom,并且将在其中许多视图上调用list.concat(values)。 因此,即使您只有一个连接,这实际上也不是更有效。

如果您不打算使用React生命周期方法,而仅打算使用纯渲染功能,那么您可能应该考虑其他仅关注该工作的类似选项,例如deku(可与Redux一起使用)

以我自己的经验,在较旧的移动设备(例如Nexus4)上,使用React不能达到令人满意的效果,尤其是当您将文本输入链接到原子状态时。

将数据连接到子组件

这是react-redux通过使用list.push(value)所建议的。因此,当状态更改仅与更深的子项有关时,您只需渲染该子项,而不必每次都像上下文提供者一样渲染顶级组件(redux / intl / 自定义...)或主应用布局。 您还避免在其他子项上调用list.concat(values),因为它已经烘焙到侦听器中了。 调用很多非常快的侦听器可能比渲染每个中间的React组件要快,并且它还可以减少传递道具的样板,因此对我来说,与React一起使用是有意义的。

还要注意,身份比较非常快,您可以在每次更改时轻松地进行很多比较。 记住Angular的肮脏检查:有些人确实设法用它来构建真实的应用程序! 身份比较要快得多。


了解你的问题

我不确定您是否能完全理解所有问题,但我知道您的视图中包含10万个项目,并且您想知道是否应对所有这10万个项目使用list.push(value),因为在每次更改时调用100k侦听器似乎代价很高。

这个问题似乎是使用UI进行功能编程的本质所固有的:列表已更新,因此您必须重新渲染列表,但是不幸的是,这是一个很长的列表,而且效率似乎很低...使用Backbone,您可以破解 只能渲染孩子的东西。 即使使用React渲染那个孩子,您也将以命令式的方式触发渲染,而不仅仅是声明“列表更改时,重新渲染它”。


解决你的问题

显然,连接100k列表项似乎很方便,但是由于调用100k react-redux侦听器,即使它们很快,也无法实现。

现在,如果您连接100k个项目的大列表,而不是分别连接每个项目,则只调用一个react-redux侦听器,然后必须以一种有效的方式呈现该列表。


天真的解决方案

遍历100k项以渲染它们,导致99999项在list.push(value)中返回false,并进行一次重新渲染:

list.map(item => this.renderItem(item))

性能解决方案1:自定义list.push(value) +商店增强器

React-Redux的list.push(value)方法只是一个高阶组件(HOC),可将数据注入到包装的组件中。 为此,它为每个连接的组件注册了一个list.concat(values)侦听器。

如果要连接单个列表的100k项,这是应用程序中值得优化的关键路径。 您可以使用自己的一个来代替使用默认的list.push(value)

  1. 店铺促销

公开其他方法list.push(value)

包装list.push(value),以便每当调度与某个项目相关的操作时,您都将调用该项目的注册侦听器。

可以通过redux-batched-subscribe获得此实现的灵感来源。

  1. 自定义连接

使用以下API创建高阶组件:

Item = connectItem(Item)

HOC可以期望拥有list.push(value)属性。 它可以使用React上下文中的Redux增强存储,然后注册其侦听器:store.listenForItemChanges(itemId)。原始store.listenForItemChanges(itemId)的源代码可以用作基础启发。

  1. 仅当项目更改时,HOC才会触发重新渲染

相关答案:[https://stackoverflow.com/a/34991164/82609]

相关的react-redux问题:[https://github.com/rackt/react-redux/issues/269]

性能解决方案2:监听子组件内部的事件

还可以使用redux-dispatch-subscribe或类似方法在组件中直接侦听Redux动作,以便在首次列表渲染后,您可以直接在item组件中侦听更新并覆盖父列表的原始数据。

class MyItemComponent extends Component {
  state = {
    itemUpdated: undefined, // Will store the local
  };
  componentDidMount() {
    this.unsubscribe = this.props.store.addDispatchListener(action => {
      const isItemUpdate = action.type === "MY_ITEM_UPDATED" && action.payload.item.id === this.props.itemId;
      if (isItemUpdate) {
        this.setState({itemUpdated: action.payload.item})
      }
    })
  }
  componentWillUnmount() {
    this.unsubscribe();
  }
  render() {
    // Initially use the data provided by the parent, but once it's updated by some event, use the updated data
    const item = this.state.itemUpdated || this.props.item;
    return (
      <div>
        {...}
      </div>
    );
  }
}

在这种情况下list.push(value)的性能可能不是很高,因为您仍会创建10万个订阅。 您宁愿使用诸如2702478124465521521666之类的API构建类似于list.concat(values)的优化中间件,将项目侦听器存储为映射,以快速查找要运行的正确侦听器...


性能解决方案3:向量尝试

一种性能更高的方法将考虑使用像vector trie这样的持久数据结构:

Trie

如果将10万个项目列表表示为特里树,则每个中间节点都有可能更快地缩短渲染,这可以避免在子代中出现大量list.push(value)

该技术可以与ImmutableJS一起使用,您可以找到我对ImmutableJS所做的一些实验:反应性能:使用PureRenderMixin渲染大列表但是它有缺点,因为像ImmutableJs这样的库还没有公开公共/稳定的API来做到这一点(问题),而我的解决方案通过一些无用的中间list.push(value)节点(问题)污染了DOM。

这是一个JsFiddle,它演示了如何有效地呈现100k项的ImmutableJS列表。 初始渲染时间很长(但是我想您不会使用10万个项目初始化您的应用程序!),但是您可以注意到每次更新只会导致少量的list.push(value)。在我的示例中,我仅每秒更新第一项 ,即使列表中有10万个项目,您也只需对1102调用list.concat(values),这是可以接受的! :)

编辑:ImmutableJS似乎不太适合在某些操作上保留其不可变结构,例如在随机索引处插入/删除项目。 这是一个JsFiddle,它演示了根据列表上的操作可以期望的性能。 令人惊讶的是,如果要在大列表的末尾附加许多项,则多次调用list.push(value)似乎比调用list.concat(values)保留了更多的树结构。

顺便说一下,据记载,列表在修改边缘时是有效的。 我不认为在给定索引处添加/删除这些不良性能与我的技术有关,而与底层ImmutableJs List实现有关。

列表实现了Deque,并有效地从末尾(推动,弹出)和开始(未移位,移位)进行添加和删除。

Sebastien Lorber answered 2020-01-14T16:09:12Z
4 votes

这可能是比您正在寻找的更普遍的答案,但是从广义上讲:

  1. Redux文档的建议是在组件层次结构中连接相当高的React组件。 请参阅本节。。这样可以保持可管理的连接数,然后可以将更新的prop传递到子组件中。
  2. React的部分功能和可扩展性来自避免呈现不可见的组件。 例如,不是在DOM元素上设置invisible类,而是在React中我们根本不渲染组件。 不变的组件的渲染也完全不是问题,因为虚拟DOM差异化过程优化了底层DOM交互。
mjhm answered 2020-01-14T16:09:42Z
translate from https://stackoverflow.com:/questions/34782249/can-a-react-redux-app-really-scale-as-well-as-say-backbone-even-with-reselect