Rebone-Redux应用程序是否真的可以扩展,比如Backbone?即使重新选择。在移动

时间:2016-01-14 05:22:28

标签: performance backbone.js mobile reactjs redux

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

对商店的每次更改,即使与组件无关,仍然会在商店的shouldComponentUpdate路径上运行===并进行简单的reselect测试。那很快,对吗?当然,也许一次。但N次,每次更改?设计的这一根本性变化让我质疑Redux的真正可扩展性。

作为进一步优化,您可以使用notify批量处理所有_.debounce次来电。即便如此,对每个商店进行N ===测试更改处理其他逻辑,例如视图逻辑,似乎是达到目的的手段。

我正致力于健康与健康。具有数百万用户的健身社交移动网络混合应用程序正在从 Backbone转换为Redux 。在此应用程序中,向用户呈现可滑动的界面,允许他们在不同的视图堆栈之间导航,类似于Snapchat,除了每个堆栈具有无限深度。在最流行的视图类型中,无限卷轴有效地处理诸如帖子之类的馈送项的加载,渲染,附加和分离。对于参与的用户,滚动数百或数千个帖子,然后输入用户的订阅源,然后输入其他用户的订阅源等,这种情况并不少见。即使经过大量优化,连接组件的数量也会变得非常大。

另一方面,Backbone的设计允许每个视图精确地聆听影响它的模型,将N减少到常数。

我是否遗漏了某些内容,或者Redux对于大型应用程序存在根本缺陷?

2 个答案:

答案 0 :(得分:87)

这不是Redux恕我直言所固有的问题。

顺便说一下,您应该尝试使用类似react-infinite之类的lib来伪造它,而不是尝试渲染100k组件,而只是渲染可见(或接近)项目你的清单。即使您成功渲染并更新了100k列表,它仍然不具备高性能并且需要大量内存。以下是一些LinkedIn advices

这个anwser会认为您仍尝试在DOM中呈现100k可更新项目,并且您不希望在每次更改时调用100k侦听器(store.subscribe())。

2所学校

在以功能方式开发UI应用程序时,您基本上有两个选择:

始终从顶部渲染

它运作良好但涉及更多样板。它不完全是建议的Redux方式,但可以实现,有些drawbacks。请注意,即使您设法使用单个redux连接,仍然需要在许多地方调用大量shouldComponentUpdate。如果您有无限的视图堆栈(如递归),则必须将所有中间视图渲染为虚拟dom,并且将在其中许多视图上调用shouldComponentUpdate。因此,即使您只有一个连接,这也不是更有效。

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

根据我自己的经验,使用React在旧版移动设备(例如我的Nexus4)上表现不够,特别是如果您将文本输入链接到原子状态。

将数据连接到子组件

这是react-redux使用connect建议的内容。因此,当状态发生变化并且它只与更深层的子项相关时,您只需渲染该子项,而不必像上下文提供程序(redux / intl / custom ...)那样每次渲染顶级组件,也不必主应用布局。您还可以避免在其他孩子身上调用shouldComponentUpdate,因为它已经在听众中被烘焙了。调用很多非常快速的侦听器可能比每次渲染中间反应组件都要快,而且它还允许减少很多道具传递样板,所以对我来说当与React一起使用时是有意义的。

另请注意,身份比较非常快,您可以在每次更改时轻松完成很多操作。记住Angular的脏检查:有些人确实设法使用它来构建真正的应用程序!身份比较要快得多。

了解您的问题

我不确定完全理解你的所有问题,但我知道你有100k项目的观点,你想知道是否应该使用connect所有这些100k项目,因为拨打100k听众在每一个变化看来都很昂贵。

这个问题似乎与使用UI进行函数式编程的本质有关:列表已更新,因此您必须重新呈现列表,但不幸的是它是一个非常长的列表,它似乎无效...使用Backbone你可以破解只能渲染孩子的东西。即使你使用React渲染那个孩子,你也会以命令的方式触发渲染,而不是仅仅在声明变化时声明渲染,重新渲染它#34;。

解决您的问题

显然,连接100k列表项看起来很方便,但由于调用了100k react-redux监听器,即使它们很快,也不是很有效。

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

天真的解决方案

迭代100k个项目来渲染它们,导致99999个项目在shouldComponentUpdate中返回false并且单个重新渲染:

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

Performant解决方案1:自定义connect +商店增强器

React-Redux的connect方法只是一个Higher-Order Component(HOC),它将数据注入到包装组件中。为此,它为每个连接的组件注册一个store.subscribe(...)侦听器。

如果您想连接单个列表中的100k项,那么这是您的应用程序值得优化的关键路径。您可以构建自己的默认connect,而不是使用默认的store.subscribeItem(itemId,listener)

  1. 商店增强器
  2. 公开其他方法dispatch

    换行Item = connectItem(Item) ,以便每当调度与项目相关的操作时,您就会调用该项目的已注册监听器。

    此实施的灵感来源可以是redux-batched-subscribe

    1. 自定义连接
    2. 使用以下API创建高阶组件:

      itemId

      HOC可以预期store.subscribeItem(itemId,callback)属性。它可以使用React上下文中的Redux增强存储,然后注册其侦听器:connect。原始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> ); } } 的源代码可以作为基础灵感。

      1. 如果项目发生变化,HOC将仅触发重新呈现
      2. 相关答案:https://stackoverflow.com/a/34991164/82609

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

        Performant solution 2:监听子组件内的事件

        也可以使用redux-dispatch-subscribe或类似内容直接在组件中侦听Redux操作,以便在第一个列表渲染后,直接​​监听项目组件中的更新并覆盖原始数据。父母名单。

        redux-dispatch-subscribe

        在这种情况下,redux-dispatch-subscribe可能效率不高,因为您仍会创建100k订阅。您宁愿构建自己的优化中间件,类似于store.listenForItemChanges(itemId),使用类似shouldComponentUpdate的API,将项侦听器存储为地图,以便快速查找要运行的正确侦听器...

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

        更高效的方法是考虑使用像vector trie这样的持久性数据结构:

        Trie

        如果您将100k项目列表表示为trie,则每个中间节点都可以更快地缩短渲染时间,这样可以避免孩子中出现大量<span>

        此技术可与ImmutableJS一起使用,您可以找到我使用ImmutableJS进行的一些实验:React performance: rendering big list with PureRenderMixin 但它有一些缺点,因为像ImmutableJs这样的lib还没有暴露公共/稳定的API(issue),我的解决方案用一些无用的中间shouldComponentUpdate节点污染了DOM(issue )。

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

        编辑:似乎ImmutableJS在某些操作中保留其不可变结构并不是那么好,例如在随机索引处插入/删除项目。这是一个JsFiddle,它根据列表中的操作演示了您可以预期的性能。令人惊讶的是,如果您想在大型列表的末尾附加许多项目,多次调用list.concat(values)似乎比调用{{1}}保留了更多的树结构。

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

          

        列表实现Deque,从末尾(推,弹)和开始(非移位,移位)有效添加和删除。

答案 1 :(得分:5)

这可能是一个比你正在寻找的更普遍的答案,但广泛地说:

  1. Redux文档的建议是在组件层次结构中将React组件连接得相当高。 See this section.。这样可以保持连接数的可管理性,然后您可以将更新的道具传递给子组件。
  2. React的部分功能和可扩展性来自于避免渲染隐形组件。例如,不是在DOM元素上设置invisible类,而是在React中我们根本不渲染组件。重新渲染未更改的组件也不是问题,因为虚拟DOM差异化过程会优化低级DOM交互。