React 有没有一种方法可以在不构建虚拟树的情况下更新 DOM?

问题描述 投票:0回答:3

想象一个包含 100 个项目的列表。有一个组件可以显示所有这些。每秒应多次将一个(随机)项目移动到新位置。 React 原生方法是通过构造虚拟树来重新渲染整个组件,然后将其与之前的副本进行比较,然后修补 DOM。问题是制作虚拟树和创建差异需要时间(在我的例子中大约 50 毫秒)。

React 有没有办法跳过虚拟树的创建并计算差异?就像下面这样:

shouldComponentUpdate
将返回
false
;然后手动将一个
Node
从 DOM 中删除,并插入到另一个位置。

更新

关于这个视频,React 中有一个最坏的情况。当您仅更新 100 项中的一项时。问题是如何以最快的方式更新 DOM(不比较 100 个项目)?

这是该问题的演示这是代码

Needle in haystack problem

javascript performance reactjs
3个回答
1
投票

您只需访问

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,因为它将最大限度地减少复制或缓存数据的需要。


1
投票

我在一个特定情况下使用的一种方法(并非可以应用于所有情况)是为每个节点设置一些位置,然后使用 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 方法生成,在每个通道上设置每个节点的样式:它只是有一个预设的高度,该高度乘以您正在迭代的索引。

很抱歉,如果这不是您期望的答案,但我使用了这种方法,并且对数百个元素来说效果很好。


0
投票

一种技术是不重新创建已经存在的节点,即缓存。这是一个包含 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 中删除。如何执行此操作取决于数据随时间的变化情况,并且由于这是一个性能问题,因此没有通用的“最佳”答案。

© www.soinside.com 2019 - 2024. All rights reserved.