如何加速生成巨大DOM的React render()方法?

sbi*_*nko 23 javascript performance reactjs

HtmlTable组件

想象一下简单的HtmlTableReact组件.它根据通过dataprop 传递给它的二维数组呈现数据,并且还可以限制通过rowCountcolCount道具的列数和行数.此外,我们需要组件处理大量数据(数万行)而不需要分页.

class HtmlTable extends React.Component {
    render() {
        var {rowCount, colCount, data} = this.props;
        var rows = this.limitData(data, rowCount);

        return <table>
            <tbody>{rows.map((row, i) => {
                var cols = this.limitData(row, colCount);
                return <tr key={i}>{cols.map((cell, i) => {
                    return <td key={i}>{cell}</td>
                })}</tr>
            })}</tbody>
        </table>
    }

    shouldComponentUpdate() {
        return false;
    }

    limitData(data, limit) {
        return limit ? data.slice(0, limit) : data;
    }
}
Run Code Online (Sandbox Code Playgroud)

rowHeights道具

现在我们想让用户更改行高并动态执行.我们添加一个rowHeightsprop,它是行索引到行高的映射:

{
    1: 100,
    4: 10,
    21: 312
}
Run Code Online (Sandbox Code Playgroud)

如果为其索引指定了高度(我们也使用for ),我们更改我们的render方法以添加styleprop :<tr>shallowCompareshouldComponentUpdate

    render() {
        var {rowCount, colCount, data, rowHeights} = this.props;
        var rows = this.limitData(data, rowCount);

        return <table>
            <tbody>{rows.map((row, i) => {
                var cols = this.limitData(row, colCount);
                var style = rowHeights[i] ? {height: rowHeights[i] + 'px'} : void 0;
                return <tr style={style} key={i}>{cols.map((cell, i) => {
                    return <td key={i}>{cell}</td>
                })}</tr>
            })}</tbody>
        </table>
    }

    shouldComponentUpdate(nextProps, nextState) {
        return shallowCompare(this, nextProps, nextState);
    }
Run Code Online (Sandbox Code Playgroud)

因此,如果用户传递rowHeights带有值的道具{1: 10},我们只需要更新一行 - 第二行.

性能问题

但是,为了做差异,React必须重新运行整个渲染方法并重新创建成千上万的<tr>s.这对于大型数据集来说非常慢.

我想过使用shouldComponentUpdate,但它没有帮助 - 瓶颈发生在我们甚至尝试更新之前<tr>.在整个桌子的重新创建过程中发生了瓶颈,以便进行差异化.

我想到的另一件事是缓存render结果,然后splice更改行,但似乎完全没有使用React的目的.

有没有办法不重新运行"大" render功能,如果我知道只有一小部分会改变?

编辑:显然,缓存是要走的路......例如,这里讨论了React的Github中的类似问题.而React Virtualized似乎正在使用一个单元缓存(尽管我可能会遗漏一些东西).

编辑2:不是.存储和重用组件的"标记"仍然很慢.其中大部分来自协调DOM,这是我应该期待的.好吧,现在我完全迷失了.这就是我准备"标记"所做的:

    componentWillMount() {
        var {rowCount, colCount, data, rowHeights={}} = this.props;
        var rows = this.limitData(data, rowCount);
        this.content = <table>
            <tbody>{rows.map((row, i) => {
                var cols = this.limitData(row, colCount);
                var style = rowHeights[i] ? {height: rowHeights[i] + 'px'} : void 0;
                return <tr style={style} key={i}>{cols.map((cell, i) => {
                    return <td key={i}>{cell}</td>
                })}</tr>
            })}</tbody>
        </table>
    }

    render() {
        return this.content
    }
Run Code Online (Sandbox Code Playgroud)

Kyl*_*ley 18

对于这种特殊情况,我建议其他人提到的react-virtualizedfixed-data-table.两个组件都将通过延迟加载数据来限制DOM交互,即仅渲染表中可见的部分.

更一般地说,MobX文档有一个关于反应性能出色页面.请检查一下.以下是要点.

1.使用许多小部件

mobx-react@observer组件将跟踪它们使用的所有值,并在其中任何值发生变化时重新呈现.因此,组件越小,重新渲染的变化就越小; 这意味着您的用户界面的更多部分可以彼此独立地呈现.

observer 允许组件独立于其父级进行渲染

2.在专用组件中渲染列表

渲染大型集合时尤其如此.React在渲染大型集合方面是出了名的糟糕,因为协调程序必须在每次集合更改时评估集合生成的组件.因此,建议使用仅映射集合并对其进行渲染的组件,并不再渲染其他内容:

3.不要使用数组索引作为键

不要使用数组索引或将来可能更改的任何值作为键.如果需要,为您的对象生成id.另见此博客.

4.最近取消引用值

当使用mobx-react时,建议尽可能晚地取消引用值.这是因为MobX将重新呈现自动取消引用可观察值的组件.如果在组件树中更深层次地发生这种情况,则需要重新渲染的组件越少.

5.尽早绑定功能

本技巧一般适用于React和使用PureRenderMixin的库,尽量避免在渲染方法中创建新的闭包.

示例(未经测试)

import { observer } from 'mobx-react';
import { observable } from 'mobx';


const HtmlTable = observer(({
  data,
}) => {
  return (
    <table>
      <TBody rows={data} />
    </table>
  );
}

const TBody = observer(({
  rows,
}) => {
  return (
    <tbody>
      {rows.map((row, i) => <Row row={row} />)}
    </tbody>
  );
});

const Row = observer(({
  row,
}) => {
  return (
    <tr key={row.id} style={{height: row.rowHeight + 'px'}}>
      {row.cols.map((cell, i) => 
        <td key={cell.id}>{cell.value}</td>
      )}
    </tr>
  );
});

class DataModel {
  @observable rows = [
    { id: 1, rowHeight: 10, cols: [{ id: 1, value: 'one-1' }, { id: 2, value: 'one-2' }] },
    { id: 2, rowHeight: 5,  cols: [{ id: 1, value: 'two-1' }, { id: 2, value: 'two-2' }] },
    { id: 3, rowHeight: 10,  cols: [{ id: 1, value: 'three-1' }, { id: 2, value: 'three-2' }] },
  ];
  @observable rowLimit = 10;
  @observable colLimit = 10;

  @computed
  get limitedRows() {
    return this.limitData(this.rows, this.rowLimit).map(r => {
      return { id: r.id, cols: this.limitData(r.col, this.colLimit) };
    });
  }

  limitData(data, limit) {
    return limit ? data.slice(0, limit) : data;
  }
}

const data = new DataModel();

React.render(<HtmlTable data={data.limitedRows} />, document.body);
Run Code Online (Sandbox Code Playgroud)

来源:


Piy*_*oor 2

将每一行作为子组件并将 props 传递给该子组件。该子组件将拥有自己的状态,并且可以在不影响所有行的情况下进行更改

class HtmlTable extends React.Component {
   render() {
       var {rowCount, colCount, data} = this.props;
       var rows = this.limitData(data, rowCount);

       return <table>
           <tbody>{rows.map((row, i) => {
               var cols = this.limitData(row, colCount);

               return (<Row cols={cols} key={i}/>)
           })}</tbody>
       </table>
   }

   shouldComponentUpdate() {
       return false;
   }

   limitData(data, limit) {
       return limit ? data.slice(0, limit) : data;
   }
}
Run Code Online (Sandbox Code Playgroud)