React是否有办法在构建虚拟树时更新DOM?

时间:2015-05-18 12:33:58

标签: javascript performance reactjs

想象一下包含100个项目的列表。有一个组件显示所有这些组件。每秒几次,一个(随机)项目应该移动到一个新的位置。 React天真的方法是通过构造虚拟树来重新渲染整个组件,然后使用先前的副本对其进行区分,然后对DOM进行修补。问题是制作虚拟树并创建差异需要花费时间(在我的情况下大约需要50毫秒)。

React有没有办法跳过虚拟树的创建并计算差异?如下所示:shouldComponentUpdate将返回false;然后手动将一个Node从DOM中移除,并插入另一个位置。

更新

关于this video,React中存在最糟糕的情况。当您只更新其中100个项目中的一个项目时。问题是如何以最快的方式更新DOM (不分散100个项目)?

问题

This is the demothis is the code

Needle in haystack problem

3 个答案:

答案 0 :(得分:1)

您只需访问componentDidMount中的DOM。

但是因为你遇到了性能问题。我会尝试让你的孩子渲染功能"纯粹"。你可以使用PureRenderMixin,但由于mixin可能不在你身边,你可以

import shallowEqual from 'react/lib/shallowEqual';

然后在你的shouldComponentUpdate函数中执行

shouldComponentUpdate(nextProps, nextState) { return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState); }

另请考虑使用Immutable-JS将100个项目保存为Map,因为这样可以最大限度地减少复制或缓存数据的需要。

答案 1 :(得分:1)

我在一个特定情况下使用的方法(不适用于每种情况)只是为每个节点设置一些位置,然后用CSS设置它的位置。

类似的东西:

<div class="parent">
  <div data-position="3"></div>
  <div data-position="1"></div>
  <div data-position="2"></div>
</div>

虽然position属性根据某些排序顺序而改变,但节点的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重新计算,但需要重新计算渲染树。但是,如果您每秒多次更改元素的位置,则只进行一次重新计算(因为只要您没有执行任何re-layout,浏览器就会尝试应用批量更新。)

CSS&#39; top&#39;可以使用Less / Sass之类的工具或使用普通JS生成属性,甚至可以通过组件中的render方法生成属性,在每次传递中设置每个节点的样式:它只具有预设的高度那将被你正在迭代的索引乘以。

很抱歉,如果这不是您期望的那种答案,但我使用了这种方法并为我提供了数百种元素。

答案 2 :(得分:0)

一种技术是不重新创建已经存在的节点,即缓存。这里有一个demo,其中包含1,000个节点,每秒都会从数组中删除一个随机项,并在随机位置添加一个项。使用反应的开发版本,我的笔记本电脑上的更新需要10毫秒。

我说了很多,但我有一个大显示器,我一次只能看到70个这样的物品而且它太多了。这里最好的答案是不从性能和ux角度渲染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,但我们正在谈论渲染()的几分之一毫秒,以及一个非常快速的协调,甚至没有浅等于。

还需要注意的是此功能的签名:

renderOrGetFromCache(id, item)

第一个参数取代了我们的shouldComponentUpdate,这意味着它需要唯一地标识一个值。如果您碰巧更改了对象的.text属性,但ID相同,则无法更新。要解决此问题,您可以更改传递给renderOrGetFromCache的内容。这样,它会对呈现的项进行正常的差异化,因为键是相同的,但所有其他节点仍然是===项检查。

这里的主要交易是您需要手动清理。按原样,此代码将泄漏内存。如果某个项目不再呈现或已更改,则需要将其从_nodeCache中删除。你如何做到这一点取决于你的数据如何随着时间的推移而变化,并且因为这是一个性能问题,所以没有普遍的“最佳”数据。答案。