将CellMeasurer与MultiGrid一起使用

时间:2017-03-06 15:58:35

标签: react-virtualized

我可能正在尝试做一些不受支持的事情,但我正在尝试使用react-virtualized的CellMeasurer和MultiGrid。我还使用ScrollSync来检测用户何时向右滚动并显示/隐藏指示符。

有一点需要注意的是,我有一个标签控件来操纵哪些数据(包括行和列)。当数据发生变化时,我在redux商店中设置了一个标志,并使用它来重新测量我的单元格。

它与我的期望非常接近。我第一次进入新选项卡时,所有单元格都正确测量,但有两个例外。

1)第一列(1个固定列)重新测量,但左上角和左下角网格的宽度不会更新。这在新测量和默认大小之间留下了空隙。滚动后,这会自行修复 - 非常确定,因为我有ScrollSync。

滚动之前 enter image description here 滚动后 enter image description here

2)列索引1永远不会小于默认宽度。这是第一个非固定列。 enter image description here enter image description here 适用于更大的内容: enter image description here

然后,主要问题是当我返回之前已经显示过的标签时。发生这种情况时,即使我的新数据标志仍然触发重新测量,上一个选项卡中存在的列的测量结果也会延续。我想我需要做一些清除缓存的事情,但到目前为止我的尝试导致所有列都达到默认宽度。是否有一定的CellMeasurerCache.clearAllMultiGrid.measureAllCellsMultiGrid.recomputeGridSize序列可以在我这里正常使用?

渲染

  render() {
    const { tableName, ui } = this.props;
    const dataSet = this.getFinalData();
    console.log('rendering');

    return (
      <ScrollSync>
        {({
          // clientHeight,
          // scrollHeight,
          // scrollTop,
          clientWidth, // width of the grid
          scrollWidth, // width of the entire page
          scrollLeft, // how far the user has scrolled
          onScroll,
        }) => {
          // if we have new daya, default yo scrolled left
          const newData = Ui.getTableNewData(ui, tableName);

          const scrolledAllRight = !newData &&
              (scrollLeft + clientWidth >= scrollWidth);
          const scrolledAllLeft = newData || scrollLeft === 0;

          return (
            <AutoSizer>
              {({ width, height }) => {
                const boxShadow = scrolledAllLeft ? false :
                    '1px -3px 3px #a2a2a2';
                return (
                  <div className="grid-container">
                    <MultiGrid
                      cellRenderer={this.cellRenderer}
                      columnWidth={this.getColumnWidth}
                      columnCount={this.getColumnCount()}
                      fixedColumnCount={1}
                      height={height}
                      rowHeight={this.getRowHeight}
                      rowCount={dataSet.length}
                      fixedRowCount={1}
                      deferredMeasurementCache={this.cellSizeCache}
                      noRowsRenderer={DataGrid.emptyRenderer}
                      width={width}
                      className={classNames('data-grid', {
                        'scrolled-left': scrolledAllLeft,
                      })}
                      onScroll={onScroll}
                      styleBottomLeftGrid={{ boxShadow }}
                      ref={(grid) => {
                        this.mainGrid = grid;
                      }}
                    />
                    <div
                      className={classNames('scroll-x-indicator', {
                        faded: scrolledAllRight,
                      })}
                    >
                      <i className="fa fa-fw fa-angle-double-right" />
                    </div>
                  </div>
                );
              }}
            </AutoSizer>
          );
        }}
      </ScrollSync>
    );
  }

单元格渲染器

  cellRenderer({ columnIndex, rowIndex, style, parent }) {
    const data = this.getFinalData(rowIndex);
    const column = this.getColumn(columnIndex);

    return (
      <CellMeasurer
        cache={this.cellSizeCache}
        columnIndex={columnIndex}
        key={`${columnIndex},${rowIndex}`}
        parent={parent}
        rowIndex={rowIndex}
        ref={(cellMeasurer) => {
          this.cellMeasurer = cellMeasurer;
        }}
      >
        <div
          style={{
            ...style,
            maxWidth: 500,
          }}
          className={classNames({
            'grid-header-cell': rowIndex === 0,
            'grid-cell': rowIndex > 0,
            'grid-row-even': rowIndex % 2 === 0,
            'first-col': columnIndex === 0,
            'last-col': columnIndex === this.getColumnCount(),
          })}
        >
          <div className="grid-cell-data">
            {data[column.key]}
          </div>
        </div>
      </CellMeasurer>
    );
  }

组件生命周期

  constructor() {
    super();

    this.cellSizeCache = new CellMeasurerCache({
      defaultWidth: 300,
    });

    // used to update the sizing on command
    this.cellMeasurer = null;
    this.mainGrid = null;

    // this binding for event methods
    this.sort = this.sort.bind(this);
    this.cellRenderer = this.cellRenderer.bind(this);
    this.getColumnWidth = this.getColumnWidth.bind(this);
    this.getRowHeight = this.getRowHeight.bind(this);
  }

  componentDidMount() {
    this.componentDidUpdate();

    setTimeout(() => {
      this.mainGrid.recomputeGridSize();
      setTimeout(() => {
        this.mainGrid.measureAllCells();
      }, 1);
    }, 1);
  }

  componentDidUpdate() {
    const { tableName, ui } = this.props;

    // if we did have new data, it is now complete
    if (Ui.getTableNewData(ui, tableName)) {
      console.log('clearing');
      setTimeout(() => {
        this.mainGrid.measureAllCells();
        setTimeout(() => {
          this.mainGrid.recomputeGridSize();
        }, 1);
      }, 1);
      this.props.setTableNewData(tableName, false);
    }
  }

编辑 Here is a plunker。这个例子显示了我解释的大部分内容。它也为行提供了比预期更多的高度(不能说明与我的其他实现有什么不同)

3 个答案:

答案 0 :(得分:2)

第一个建议:不要使用ScrollSync。只需直接使用onScroll的{​​{1}}属性即可。我认为MultiGrid对于这种情况来说是过度的。

第二个建议:如果可能,请避免使用ScrollSync同时测量宽度高度,因为这需要贪婪地测量整个CellMeasurer以计算最大单元格在每列+行中。在你的Plnkr中记录了一个关于此事件的开发警告,但它被另一个日志记录掩埋了:

  

CellMeasurerCache应该只测量单元格的宽度或高度。您已配置CellMeasurerCache来测量它们。这将导致性能不佳。

不幸的是,为了解决你的问题,我相信你已经发现了GridCellMeasurer之间互动的一些缺陷。

编辑这些缺陷已在9.2.3版本中得到解决。请升级。 :)

您可以看到MultiGrid + CellMeasurer here的演示,可以看到源代码here

答案 1 :(得分:0)

答案 2 :(得分:0)

你可以看看下面的代码。我使用 CellMeasurer 来制作单元格大小。列宽和行高都将在运行时测量。

import classnames from 'classnames';
import React, {Component} from 'react';
import {AutoSizer, CellMeasurer, CellMeasurerCache, MultiGrid} from 'react-virtualized';
import './Spreadsheet.css';

const LETTERS = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ';

export default class MySlide extends Component {
    constructor(props, context) {
        super(props, context);

        this.state = {
            cellValues: {},
            focusedColumnIndex: null,
            focusedRowIndex: null
        };

        this._cache = new CellMeasurerCache({
            defaultHeight: 30,
            defaultWidth: 150
        });

        this._cellRenderer = this._cellRenderer.bind(this);
        this._setRef = this._setRef.bind(this);
    }

    getRandomInt(min, max) {
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    componentWillUpdate(nextProps, nextState) {
        const {cellValues, focusedColumnIndex, focusedRowIndex} = this.state;

        if (
            focusedColumnIndex !== nextState.focusedColumnIndex ||
            focusedRowIndex !== nextState.focusedRowIndex
        ) {
            this._multiGrid.forceUpdate();
        } else if (cellValues !== nextState.cellValues) {
            this._multiGrid.forceUpdate();
        }
    }


    render() {
        return (
            <AutoSizer disableHeight>
                {({width}) => (
                    <MultiGrid
                        cellRenderer={this._cellRenderer}
                        columnCount={LETTERS.length}
                        fixedColumnCount={1}
                        fixedRowCount={1}
                        height={600}
                        columnWidth={this._cache.columnWidth}
                        rowHeight={this._cache.rowHeight}
                        deferredMeasurementCache={this._cache}
                        overscanColumnCount={0}
                        overscanRowCount={0}
                        ref={this._setRef}
                        rowCount={100}
                        style={{
                            border: '1px solid #dadada',
                            whiteSpace: 'pre',
                            overflowX: 'hidden',
                            textOverflow: 'ellipsis'
                        }}
                        styleBottomLeftGrid={{
                            backgroundColor: '#ffffff'
                        }}
                        styleTopLeftGrid={{
                            backgroundColor: '#f3f3f3',
                            borderBottom: '4px solid #bcbcbc',
                            borderRight: '4px solid #bcbcbc'
                        }}
                        styleTopRightGrid={{
                            backgroundColor: '#f3f3f3'
                        }}
                        width={width}
                    />


                )}
            </AutoSizer>
        );
    }

    _cellRenderer({columnIndex, key, parent, rowIndex, style}) {
        if (columnIndex === 0 && rowIndex === 0) {
            return <div key={key} style={style}/>
        } else if (columnIndex === 0) {
            return this._cellRendererLeft({columnIndex, key, parent, rowIndex, style})
        } else if (rowIndex === 0) {
            return this._cellRendererTop({columnIndex, key, parent, rowIndex, style})
        } else {
            return this._cellRendererMain({columnIndex, key, parent, rowIndex, style})
        }
    }

    _cellRendererLeft = ({columnIndex, key, parent, rowIndex, style}) => {
        const {focusedRowIndex} = this.state;

        return (
            <CellMeasurer
                cache={this._cache}
                columnIndex={columnIndex}
                key={key}
                parent={parent}
                rowIndex={rowIndex}>
                <div
                    className={classnames('FixedGridCell', {
                        FixedGridCellFocused: rowIndex === focusedRowIndex
                    })}
                    key={key}
                    style={{
                        ...style,
                        whiteSpace: 'nowrap',
                        padding: '16px'
                    }}
                >
                    {rowIndex}
                </div>
            </CellMeasurer>
        );
    }

    _cellRendererMain = ({columnIndex, key, parent, rowIndex, style}) => {
        const {cellValues, focusedColumnIndex, focusedRowIndex} = this.state;

        const value = cellValues[key] || '';


        const isFocused = (
            columnIndex === focusedColumnIndex &&
            rowIndex === focusedRowIndex
        );

        return (
            <CellMeasurer
                cache={this._cache}
                columnIndex={columnIndex}
                key={key}
                parent={parent}
                rowIndex={rowIndex}>
                <div
                    key={key}
                    style={{
                        ...style,
                        whiteSpace: 'nowrap',
                        padding: '16px'
                    }}
                    className={classnames('MainGridCell', {
                        MainGridCellFocused: isFocused,
                    })}
                    /*onFocus={() => this.setState({
                        focusedColumnIndex: columnIndex,
                        focusedRowIndex: rowIndex
                    })}
                    onChange={(event) => {
                        this.setState({
                            cellValues: {
                                ...cellValues,
                                [key]: event.target.value
                            }
                        })
                    }}*/>{rowIndex + ',' + columnIndex}
                    {columnIndex % 3 === 0 && ' This is a long sentence'}<br/>
                    {rowIndex % 4 === 0 && <br/>}
                    {rowIndex % 4 === 0 && 'This is a another line'}
                    {rowIndex % 6 === 0 && <br/>}
                    {rowIndex % 6 === 0 && 'This is a long sentence'}
                </div>
            </CellMeasurer>
        );
    }

    _cellRendererTop = ({columnIndex, key, parent, rowIndex, style}) => {
        const {focusedColumnIndex} = this.state;

        return (
            <CellMeasurer
                cache={this._cache}
                columnIndex={columnIndex}
                key={key}
                parent={parent}
                rowIndex={rowIndex}>
                <div
                    className={classnames('FixedGridCell', {
                        FixedGridCellFocused: columnIndex === focusedColumnIndex
                    })}
                    key={key}
                    style={{
                        ...style,
                        whiteSpace: 'nowrap',
                    }}
                >
                    {LETTERS[columnIndex]}
                </div>
            </CellMeasurer>
        );
    }

    _setRef(ref) {
        this._multiGrid = ref;
    }
}

电子表格.css

    .GridContainer {
        height: 300px;
        position: relative;
        border: 1px solid #dadada;
        overflow: hidden;
    }
    
    .TopLeftCell {
        height: 40px;
        width: 50px;
        background-color: #f3f3f3;
        border-bottom: 4px solid #bcbcbc;
        border-right: 4px solid #bcbcbc;
    }
    
    .MainGrid {
        position: absolute !important;
        left: 50px;
        top: 40px;
    }
    
    .MainGridCell {
        display: flex;
        flex-direction: row;
        align-items: center;
        justify-content: flex-start;
        padding: 0.25rem;
        outline: 0;
        border: none;
        border-right: 1px solid #dadada;
        border-bottom: 1px solid #dadada;
        background-color: #fff;
        font-size: 1rem;
    }
    
    .MainGridCellFocused {
        box-shadow: 0 0 0 2px #4285FA inset;
    }
    
    .LeftGrid {
        position: absolute !important;
        left: 0;
        top: 40px;
        overflow: hidden !important;
    }
    
    .TopGrid {
        position: absolute !important;
        left: 50px;
        top: 0;
        height: 40px;
        overflow: hidden !important;
    }
    
    .FixedGridCell {
        display: flex;
        flex-direction: row;
        align-items: center;
        justify-content: center;
        background-color: #f3f3f3;
        border-right: 1px solid #ccc;
        border-bottom: 1px solid #ccc;
    }
    
    .FixedGridCellFocused {
        background-color: #dddddd;
    }