想象一个包含 100 个项目的列表。有一个组件可以显示所有这些。每秒应多次将一个(随机)项目移动到新位置。 React 原生方法是通过构造虚拟树来重新渲染整个组件,然后将其与之前的副本进行比较,然后修补 DOM。问题是制作虚拟树和创建差异需要时间(在我的例子中大约 50 毫秒)。
React 有没有办法跳过虚拟树的创建并计算差异?就像下面这样:
shouldComponentUpdate
将返回false
;然后手动将一个 Node
从 DOM 中删除,并插入到另一个位置。
更新
关于这个视频,React 中有一个最坏的情况。当您仅更新 100 项中的一项时。问题是如何以最快的方式更新 DOM(不比较 100 个项目)?
您只需访问
componentDidMount
中的 DOM。
但是由于您遇到了性能问题。我会尝试让你的孩子渲染函数“纯粹”。您可以使用 PureRenderMixin ,但由于 mixins 可能不会留在您身边
import shallowEqual from 'react/lib/shallowEqual';
然后在你的
shouldComponentUpdate
函数中执行
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
}
还可以考虑使用 Immutable-JS 将 100 个项目保存为 Map,因为它将最大限度地减少复制或缓存数据的需要。
我在一个特定情况下使用的一种方法(并非可以应用于所有情况)是为每个节点设置一些位置,然后使用 CSS 设置其位置。
类似:
<div class="parent">
<div data-position="3"></div>
<div data-position="1"></div>
<div data-position="2"></div>
</div>
虽然位置属性根据某种排序顺序而变化,但节点的 DOM 位置保持不变。问题是你需要用 CSS 设置每个节点的视觉位置,如下所示:
.parent {position: relative}
.parent div {
position: absolute;
height: <something>px;
width: 100%;
}
.parent div[data-position="1"] { top: 0; }
.parent div[data-position="2"] { top: <something>px; }
.parent div[data-position="3"] { top: <something * 2>px; }
这可以使浏览器免于执行 DOM 重新计算,尽管它需要重新计算渲染树。但是,如果您每秒多次更改元素的位置,则只会进行一次重新计算(因为只要您不执行任何重新布局,浏览器就会尝试应用批量更新)。
CSS 'top' 属性可以通过使用像 Less/Sass 这样的工具生成,或者使用普通的 JS 生成,甚至可以通过组件中的 render 方法生成,在每个通道上设置每个节点的样式:它只是有一个预设的高度,该高度乘以您正在迭代的索引。
很抱歉,如果这不是您期望的答案,但我使用了这种方法,并且对数百个元素来说效果很好。
一种技术是不重新创建已经存在的节点,即缓存。这是一个包含 1,000 个节点的 demo,每秒都会从数组中删除一个随机项目,并在随机位置添加一个项目。在我的笔记本电脑上,使用 React 的开发版本,更新需要 10 毫秒。
我经常这么说,但我有一个大显示器,一次只能看到其中 70 个项目,太多了。这里最好的答案是从性能和用户体验的角度来看不要渲染 1,000 个项目。
假设您有一个带有 id 属性的对象数组,以及其他一些东西。
class Foo {
constructor(){
this._nodeCache = {};
}
renderItem(item){
return <div key={item.id}>{item.text}</div>;
}
renderOrGetFromCache(id, item){
if (this._nodeCache[id]) {
return this._nodeCache[id];
}
this._nodeCache[id] = this.renderItem(item);
return this._nodeCache[id];
}
render(){
return (
<div>
{this.props.items.map((item) => this.renderOrGetFromCache(item.id, item))}
</div>
);
}
}
那么这与您之前所做的有什么不同?
和解之前的样子:
现在看起来像:
===
检查)?
你仍在构建虚拟 dom,但我们正在讨论 render() 的几分之一毫秒,以及非常快的协调,甚至没有浅等于。
还有一点需要注意的是这个函数的签名:
renderOrGetFromCache(id, item)
第一个参数取代了我们的shouldComponentUpdate,这意味着它需要唯一标识一个值。如果您碰巧更改了对象的 .text 属性,但 id 相同,则它不会更新。要解决此问题,您可以更改传递给 renderOrGetFromCache 的内容。这样,它将对渲染的项目进行正常的比较,因为键是相同的,但所有其他节点仍将是
===
检查。
这里的主要权衡是您需要手动处理清理工作。事实上,这段代码会泄漏内存。如果某个项目不再呈现或已更改,则需要将其从 _nodeCache 中删除。如何执行此操作取决于数据随时间的变化情况,并且由于这是一个性能问题,因此没有通用的“最佳”答案。