65.9K
CodeProject 正在变化。 阅读更多。
Home

ag-Grid (React) 单元格渲染器性能

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2020年5月4日

CPOL

4分钟阅读

viewsIcon

8257

ag-Grid 中的单元格渲染器性能

简而言之

Vanilla JS 单元格渲染器比框架渲染器更快 (就像文档建议的那样),但使用 React 组件创建的渲染器也相当不错。 遗憾的是,使用 函数 组件构建的单元格渲染器明显更差。 网格团队已经意识到这个问题,可能会很快修复它 (我已经在 ag-Grid 23.1.0 上做了测试,它在 2 天前发布)... 查看实时演示,该演示比较了渲染单元格内容的不同方式,并查看 GitHub 上的源代码

对比应用

我需要创建一些高级单元格渲染器来解决单元格编辑器的局限性(以完全控制何时停止行编辑,允许多行编辑并使其与 Redux 很好地配合)... 但在此之前,我想了解一下基于 React 的单元格渲染器与纯 JS 单元格渲染函数相比,速度会慢多少。 我想测试基本的单元格渲染器,以了解使用 frameworkComponents 进行单元格渲染所增加的开销...

点击这里 查看一个可以让你比较单元格渲染器性能的应用。 该应用程序构建了一个包含 100 行和 400 列的网格,但基于渲染器类型的选择,在任何给定时刻只有 100 列是可见的。 切换渲染器类型(即切换列的可见性)或滚动网格让你有机会看到渲染器的性能。 还有一个复选框可以让你评估 disableStaticMarkup 选项的影响。

该应用程序使用 ag-Grid 23.1.0React 16.3.1 (撰写本文时最新的版本) 完成,并在 Chrome 81、Firefox 75、Edge 44 中进行了测试。

以下是测试应用程序的外观(显示使用 React 类组件渲染的单元格)

Cell renderers test app... Click to enlarge...

如果你想在你的机器上运行该应用:克隆 该仓库,执行 npm installnpm start (就像使用 create-react-app 启动的任何其他项目一样)...

它是如何工作的?

"未设置" 选项意味着列定义完全缺少 cellRenderer 选项。“Vanilla JS 函数”将导致单元格使用以下渲染器进行渲染

export default function vanillaFunctionRenderer(params) {
    return `<span>VF: ${params.value}</span>`;
}

"React 类组件"表示以下渲染器

import React, { Component } from 'react';

export default class ReactClassRenderer extends Component {
    render() {        
        return (
            <span>RC: {this.props.value}</span>
        );
    }
}

这是 "React 函数组件" 选项的渲染器

import React from 'react';

export default function ReactFunctionRenderer(props) {    
    return (
        <span>RF: {props.value}</span>
    );
}

请注意,所有三个渲染器都做同样简单的事情:渲染一个带有单元格值的 span,并以 "VF: "、"RC: " 或 "RF: " 作为前缀,以便更容易看到哪个渲染器实际上被网格上可见的列使用。 另请注意,React 状态未被使用 (既没有类状态,也没有状态钩子)。

在下面,你可以看到 ag-Grid 是如何配置的 (注意 componentsframeworkComponentsdisableStaticMarkup 选项),以及如何通过调用 列 API setColumnsVisible 来控制列的可见性 (使用批量可见性更改比单独调用 setColumnVisible 要快得多)。

// Imports skipped

class Grid extends Component {
    constructor(props) {
        super(props);

        const [columnDefs, rowData] = generateColumnsAndRows(100, 100);

        this.state = {
            columnDefs,
            rowData,
            disableStaticMarkup: false
        }
    }

    setColumnsVisiblity = rendererType => {
        const allColumns = this.gridColumnApi.getAllColumns();

        const columnsToHide = allColumns.filter(c => c.colDef.cellRenderer !== rendererType);
        const columnsToShow = allColumns.filter(c => c.colDef.cellRenderer === rendererType);

        this.gridColumnApi.setColumnsVisible(columnsToHide, false);
        this.gridColumnApi.setColumnsVisible(columnsToShow, true);
    }

    handleGridReady = params => {
        this.gridColumnApi = params.columnApi;
    }

    handleRendererTypeChange = event => {
        this.setColumnsVisiblity(event.target.value || undefined);
    }

    handleDisableStaticMarkupOptionChange = event => {
        this.setState({
            disableStaticMarkup: event.target.checked
        });
    }

    render() {
        return (
            <>
                {/* Option controls skipped */}
                <div className="ag-theme-balham">
                    <AgGridReact
                        defaultColDef={{
                            width: 90
                        }}
                        components={{
                            vanillaFunction: vanillaFunctionRenderer
                        }}
                        frameworkComponents={{
                            reactClass: ReactClassRenderer,
                            reactFunction: ReactFunctionRenderer
                        }}
                        columnDefs={this.state.columnDefs}
                        rowData={this.state.rowData}
                        disableStaticMarkup={this.state.disableStaticMarkup}
                        onGridReady={this.handleGridReady}
                    />
                </div>
            </>
        );
    }
}

export default Grid;

最后一段相关的代码是用于填充 columnDefsrowDatagenerateColumnsAndRows 函数

const generateColumnsAndRows = (columnsPerTypeCount, rowsCount) => {
    const columnDefs = [];
    const rowData = [];

    for (let i = 0; i < columnsPerTypeCount; i++) {
        columnDefs.push({
            field: 'field_' + i,
            headerName: 'Col ' + i
        }, {
            field: 'field_vf_' + i,
            headerName: 'Col VF ' + i,
            cellRenderer: 'vanillaFunction',
            hide: true
        }, {
            field: 'field_rc_' + i,
            headerName: 'Col RC ' + i,
            cellRenderer: 'reactClass',
            hide: true
        }, {
            field: 'field_rf_' + i,
            headerName: 'Col RF ' + i,
            cellRenderer: 'reactFunction',
            hide: true
        });
    }

    // Row generation skipped

    return [columnDefs, rowData];
}

export default generateColumnsAndRows;

注意 cellRenderer 渲染器属性,并且最初只有未分配任何单元格渲染器的列是可见的。

结果

不出所料,显示单元格值的最快方法是不定义任何单元格渲染器。 假设你需要一些,最快的方法是将一个普通的 JS 渲染器分配给 components 属性。 如果你需要使用 React 组件 (分配给 frameworkComponents 属性),那么你应该坚持使用基于类的组件,直到 ag-Grid 团队改进函数组件的处理。 顺便说一句,使用类并没有什么问题,在 ag-Grid 方面,使用类组件也更容易处理其他一些事情...

单元格渲染性能是最好用眼睛“测量”的事情之一。 只需 玩玩演示应用,切换列并滚动网格,你会注意到,在 React 函数组件的情况下,可能会短暂显示重复的值,破坏用户体验

Duplicated value... Click to enlarge..

启用 disableStaticMarkup 选项有助于解决函数组件的重复值闪烁问题,但会导致基于类的渲染器的空值闪烁...

如果你在使用测试应用程序时需要比你自己的印象更多的信息,那么这里是在 Chrome 81 DevTools 中完成的性能测量

Performance measurement in Chrome DevTools... Click to enlarge..

该测量是在网格在屏幕上显示 560 个单元格且 disableStaticMarkup 设置为 false 时进行的。 时间线显示了在完成渲染器类型选择后完全重新渲染网格所花费的时间。 选择 vanilla 渲染器大约需要 1300 毫秒,React 类使用 1900 毫秒,React 函数使用 2600 毫秒。 当然,DevTools 工具的开销不小,如果没有它,网格会更快地重新渲染 - 无论如何,相对速度似乎与我的眼睛得到的印象相符。

© . All rights reserved.