恕我直言,这不是 Redux 固有的问题。
顺便说一句,与其尝试同时渲染 100k 个组件,您应该尝试使用像react-infinite或类似的东西来伪造它,并且只渲染列表中可见(或接近)的项目。即使您成功渲染和更新 100k 列表,它仍然没有性能,并且需要大量内存。以下是一些LinkedIn 的建议
此 anwser 将考虑您仍然尝试在 DOM 中呈现 100k 可更新项目,并且您不希望store.subscribe()
在每次更改时都调用100k 侦听器 ( )。
2所学校
在以功能方式开发 UI 应用程序时,您基本上有两种选择:
始终从顶部渲染
它运行良好,但涉及更多样板。这不完全是建议的 Redux 方式,但可以实现,但有一些缺点。请注意,即使您设法拥有一个 redux 连接,您仍然必须shouldComponentUpdate
在许多地方调用很多。如果您有无限的视图堆栈(如递归),您将不得不将所有中间视图渲染为虚拟 dom,并且shouldComponentUpdate
将在其中许多视图上调用。因此,即使您只有一个连接,这也不是更有效。
如果你不打算使用 React 生命周期方法而只使用纯渲染函数,那么你可能应该考虑其他类似的选项,只专注于该工作,如deku(可以与 Redux 一起使用)
根据我自己的经验,使用 React 在较旧的移动设备(例如我的 Nexus4)上性能不够,尤其是当您将文本输入链接到原子状态时。
将数据连接到子组件
这就是react-redux通过使用connect
. 因此,当状态发生变化并且仅与更深层次的子级相关时,您只需渲染该子级,而不必每次都渲染顶级组件,例如上下文提供程序(redux/intl/custom...)或主应用程序布局。您还避免调用shouldComponentUpdate
其他孩子,因为它已经融入了侦听器中。调用大量非常快的侦听器可能比每次渲染中间react组件更快,并且它还允许减少大量传递props的样板,因此对我而言,当与 React 一起使用时它是有意义的。
另请注意,身份比较非常快,您可以在每次更改时轻松完成其中的很多工作。记住 Angular 的脏检查:有些人确实设法用它构建了真正的应用程序!身份比较要快得多。
了解您的问题
我不确定是否完全理解您的所有问题,但我知道您的视图中包含 100k 项,并且您想知道是否应该使用connect
所有这些 100k 项,因为在每次更改时调用 100k 侦听器似乎代价高昂。
这个问题似乎是使用 UI 进行函数式编程的本质所固有的:列表已更新,因此您必须重新渲染列表,但不幸的是,它是一个很长的列表,而且似乎效率低下……使用 Backbone,您可以破解只渲染孩子的东西。即使您使用 React 渲染该子项,您也会以命令式方式触发渲染,而不仅仅是声明“当列表更改时,重新渲染它”。
解决您的问题
显然,连接 100k 列表项看起来很方便,但由于调用了 100k react-redux 侦听器,因此性能不佳,即使它们速度很快。
现在,如果您连接 100k 项的大列表而不是单独连接每个项,则只需调用一个 react-redux 侦听器,然后必须以有效的方式呈现该列表。
天真的解决方案
迭代 100k 个项目来渲染它们,导致 99999 个项目返回 falseshouldComponentUpdate
并重新渲染单个项目:
list.map(item => this.renderItem(item))
高性能解决方案 1:自定义connect
+ 商店增强器
的connect
阵营-终极版的方法仅仅是一个高次成分(HOC),该喷射数据到缠绕组件。为此,它store.subscribe(...)
为每个连接的组件注册一个侦听器。
如果你想连接单个列表的 100k 项,它是你的应用程序的一个值得优化的关键路径。connect
您可以构建自己的,而不是使用默认值。
- 商店增强剂
暴露一个额外的方法 store.subscribeItem(itemId,listener)
包装dispatch
以便每当与项目相关的操作被调度时,您调用该项目的注册侦听器。
这个实现的一个很好的灵感来源可以是redux-batched-subscribe。
- 自定义连接
使用如下 API 创建高阶组件:
Item = connectItem(Item)
HOC 可以期待一个itemId
属性。它可以使用来自 React 上下文的 Redux 增强存储,然后注册其侦听器:store.subscribeItem(itemId,callback)
。原始的源代码connect
可以作为基础灵感。
- HOC 只会在 item 发生变化时触发重新渲染
相关答案:https : //stackoverflow.com/a/34991164/82609
相关 react-redux 问题:https : //github.com/rackt/react-redux/issues/269
高性能解决方案 2:侦听子组件内的事件
也可以直接在组件中监听 Redux 动作,使用redux-dispatch-subscribe或类似的东西,这样在第一次列表渲染后,你可以直接在 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>
);
}
}
在这种情况下,redux-dispatch-subscribe
性能可能不是很好,因为您仍然会创建 10 万个订阅。您宁愿构建自己的优化中间件,类似于redux-dispatch-subscribe
API 之类的store.listenForItemChanges(itemId)
,将项目侦听器存储为映射,以便快速查找要运行的正确侦听器...
高性能解决方案 3:向量尝试
一种更高效的方法会考虑使用像向量树这样的持久数据结构:

如果您将 100k 项列表表示为 trie,则每个中间节点都有可能更快地使渲染短路,从而可以避免大量shouldComponentUpdate
in child。
这种技术可以与ImmutableJS一起使用,你可以找到我用 ImmutableJS 做的一些实验:react性能:用 PureRenderMixin 渲染大列表
它有缺点,但是像 ImmutableJs 这样的库还没有公开公共/稳定 API 来做到这一点(问题),我的解决方案使用一些无用的中间<span>
节点(issue)污染了 DOM 。
这是一个JsFiddle,它演示了如何有效地呈现 100k 个项目的 ImmutableJS 列表。初始渲染很长(但我猜你没有用 10 万个项目初始化你的应用程序!)但是在你注意到每次更新之后,只产生少量的shouldComponentUpdate
. 在我的示例中,我每秒只更新第一项,您会注意到即使列表有 100k 项,它也只需要像 110 次这样的调用,shouldComponentUpdate
这更容易接受!:)
编辑:似乎 ImmutableJS 在某些操作上保留其不可变结构并不是那么好,例如在随机索引处插入/删除项目。这是一个JsFiddle,它演示了根据列表上的操作您可以期望的性能。令人惊讶的是,如果您想在大列表的末尾附加许多项目,list.push(value)
多次调用似乎比调用list.concat(values)
.
顺便说一下,据记录,列表在修改边时是有效的。我不认为这些在给定索引处添加/删除的糟糕表现与我的技术有关,而是与底层的 ImmutableJs List 实现有关。
列表实现了 Deque,在末尾(push、pop)和开头(unshift、shift)都进行了有效的添加和删除。