在Redux中,对商店的每次更改都会在所有连接的组件上触发notify
。这使开发人员的工作变得非常简单,但如果你有一个具有N个连接组件的应用程序,并且N非常大,该怎么办?
对商店的每次更改,即使与组件无关,仍然会在商店的shouldComponentUpdate
路径上运行===
并进行简单的reselect
测试。那很快,对吗?当然,也许一次。但N次,每次更改?设计的这一根本性变化让我质疑Redux的真正可扩展性。
作为进一步优化,您可以使用notify
批量处理所有_.debounce
次来电。即便如此,对每个商店进行N ===
测试更改和处理其他逻辑,例如视图逻辑,似乎是达到目的的手段。
我正致力于健康与健康。具有数百万用户的健身社交移动网络混合应用程序正在从 Backbone转换为Redux 。在此应用程序中,向用户呈现可滑动的界面,允许他们在不同的视图堆栈之间导航,类似于Snapchat,除了每个堆栈具有无限深度。在最流行的视图类型中,无限卷轴有效地处理诸如帖子之类的馈送项的加载,渲染,附加和分离。对于参与的用户,滚动数百或数千个帖子,然后输入用户的订阅源,然后输入其他用户的订阅源等,这种情况并不少见。即使经过大量优化,连接组件的数量也会变得非常大。
另一方面,Backbone的设计允许每个视图精确地聆听影响它的模型,将N减少到常数。
我是否遗漏了某些内容,或者Redux对于大型应用程序存在根本缺陷?
答案 0 :(得分:87)
这不是Redux恕我直言所固有的问题。
顺便说一下,您应该尝试使用类似react-infinite之类的lib来伪造它,而不是尝试渲染100k组件,而只是渲染可见(或接近)项目你的清单。即使您成功渲染并更新了100k列表,它仍然不具备高性能并且需要大量内存。以下是一些LinkedIn advices
这个anwser会认为您仍尝试在DOM中呈现100k可更新项目,并且您不希望在每次更改时调用100k侦听器(store.subscribe()
)。
在以功能方式开发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))
connect
+商店增强器 React-Redux的connect
方法只是一个Higher-Order Component(HOC),它将数据注入到包装组件中。为此,它为每个连接的组件注册一个store.subscribe(...)
侦听器。
如果您想连接单个列表中的100k项,那么这是您的应用程序值得优化的关键路径。您可以构建自己的默认connect
,而不是使用默认的store.subscribeItem(itemId,listener)
。
公开其他方法dispatch
换行Item = connectItem(Item)
,以便每当调度与项目相关的操作时,您就会调用该项目的已注册监听器。
此实施的灵感来源可以是redux-batched-subscribe。
使用以下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>
);
}
}
的源代码可以作为基础灵感。
相关答案:https://stackoverflow.com/a/34991164/82609
相关的react-redux问题:https://github.com/rackt/react-redux/issues/269
也可以使用redux-dispatch-subscribe或类似内容直接在组件中侦听Redux操作,以便在第一个列表渲染后,直接监听项目组件中的更新并覆盖原始数据。父母名单。
redux-dispatch-subscribe
在这种情况下,redux-dispatch-subscribe
可能效率不高,因为您仍会创建100k订阅。您宁愿构建自己的优化中间件,类似于store.listenForItemChanges(itemId)
,使用类似shouldComponentUpdate
的API,将项侦听器存储为地图,以便快速查找要运行的正确侦听器...
更高效的方法是考虑使用像vector 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)
这可能是一个比你正在寻找的更普遍的答案,但广泛地说:
invisible
类,而是在React中我们根本不渲染组件。重新渲染未更改的组件也不是问题,因为虚拟DOM差异化过程会优化低级DOM交互。