Reactjs 如何使用react虚拟化列表解决可扩展行的行高调整问题?

Reactjs 如何使用react虚拟化列表解决可扩展行的行高调整问题?,reactjs,material-ui,react-virtualized,Reactjs,Material Ui,React Virtualized,我使用的可扩展面板(材质UI)是带有react虚拟化列表的行,并且在高度自动调整方面一直存在问题。我在react虚拟化站点上读过几篇SO帖子和一些关于动态行高的问题,但我有一个具体的问题,当面板展开/折叠后调整行高时,似乎存在一个“关闭一个”的问题 以下是预期的行为: 默认情况下,“行”面板已展开 用户单击可展开面板行 行面板折叠 行高度根据面板折叠进行调整 以下是第一次单击的实际行为: 默认情况下,“行”面板已展开 用户单击可展开面板行 行面板折叠 行高度不会调整为面板折叠 但是,在随后的单击

我使用的可扩展面板(材质UI)是带有react虚拟化列表的行,并且在高度自动调整方面一直存在问题。我在react虚拟化站点上读过几篇SO帖子和一些关于动态行高的问题,但我有一个具体的问题,当面板展开/折叠后调整行高时,似乎存在一个“关闭一个”的问题

以下是预期的行为:

  • 默认情况下,“行”面板已展开
  • 用户单击可展开面板行
  • 行面板折叠
  • 行高度根据面板折叠进行调整
  • 以下是第一次单击的实际行为:

  • 默认情况下,“行”面板已展开
  • 用户单击可展开面板行
  • 行面板折叠
  • 行高度不会调整为面板折叠
  • 但是,在随后的单击中,行高度确实会调整,但会变为“相反”状态,这会导致不一致-即,当单击行面板再次展开时,行高度会调整为行高度,就好像行高度已折叠一样,反之亦然。因此,当面板折叠时,后面会有一堆空白,当它从技术上展开时,行高度太小,无法看到内容
  • 我不确定除了发布代码和注意面板折叠/展开时onRowClick()正在触发之外,还应该包含哪些其他信息

    下面是父组件:

    import React, { Component } from 'react';
    import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
    import List from 'react-virtualized/dist/commonjs/List';
    import { CellMeasurer, CellMeasurerCache } from 'react-virtualized/dist/commonjs/CellMeasurer';
    import EquipSummaryRow from './EquipSummaryRow';
    import './EquipSummary.css';
    
    class EquipSummary extends Component {
      constructor(props) {
        super(props);
    
        this.cache = new CellMeasurerCache({
          fixedWidth: true,
        });
    
        this.rowRenderer = this.rowRenderer.bind(this);
        this.getDatum = this.getDatum.bind(this);
        this.onRowClick = this.onRowClick.bind(this);
      }
    
      getDatum(index) {
        const list = this.props.equipData;
    
        return list[index];
      }
    
      saveRef = (ref) => this.containerNode = ref;
    
      saveListRef = (ref) => {
        this.list = ref;
      }
    
      componentDidUpdate() {
        console.log('component updated');
        this.cache.clearAll();
        this.list.recomputeRowHeights();
      }
    
      onRowClick(e, index) {
        e.preventDefault();
        this.cache.clear(index);
        this.list.recomputeRowHeights();
        this.list.forceUpdateGrid();
      }
    
      rowRenderer({ index, key, parent, style }) {
        const datum = this.getDatum(index);
        return (
          <div key={key} style={style}>
            <CellMeasurer
              cache={this.cache}
              columnIndex={0}
              key={key}
              rowIndex={index}
              parent={parent}
            >
              {({ measure }) => (
                <EquipSummaryRow
                  onClick={(e, idx) => this.onRowClick(e, idx)}
                  measure={measure}
                  datum={datum}
                  index={index}
                />
              )}
            </CellMeasurer>
          </div>
        );
      }
    
      render() {
        console.log('rendering..');
        return (
          <div className="EquipSummary-AutoSizer" ref={this.saveRef}>
            <AutoSizer>
              {({ width, height }) => (
                <List
                  ref={this.saveListRef}
                  width={width}
                  height={height}
                  rowHeight={this.cache.rowHeight}
                  rowCount={this.props.equipData.length}
                  rowRenderer={this.rowRenderer}
                  deferredMeasurementCache={this.cache}
                  equipData={this.props.equipData}
                />
              )}
            </AutoSizer>
          </div>
        );
      }
    }
    
    export default EquipSummary;
    
    import React,{Component}来自'React';
    从“react virtualized/dist/commonjs/AutoSizer”导入AutoSizer;
    从“react virtualized/dist/commonjs/List”导入列表;
    从'react virtualized/dist/commonjs/cellmasurer'导入{cellmasurer,cellmasurercache};
    从“/EquipmSummaryRow”导入EquipmSummaryRow;
    导入“/equipmsummary.css”;
    类扩展组件{
    建造师(道具){
    超级(道具);
    this.cache=新的CellMeasureCache({
    固定宽度:对,
    });
    this.rowRenderer=this.rowRenderer.bind(this);
    this.getDatum=this.getDatum.bind(this);
    this.onRowClick=this.onRowClick.bind(this);
    }
    getDatum(索引){
    const list=this.props.equipData;
    返回列表[索引];
    }
    saveRef=(ref)=>this.containerNode=ref;
    saveListRef=(ref)=>{
    this.list=ref;
    }
    componentDidUpdate(){
    console.log(“组件更新”);
    this.cache.clearAll();
    this.list.recomputeroweights();
    }
    onRowClick(e,索引){
    e、 预防默认值();
    this.cache.clear(索引);
    this.list.recomputeroweights();
    this.list.forceUpdateGrid();
    }
    行渲染器({index,key,parent,style}){
    常量数据=此.getDatum(索引);
    返回(
    {({measure})=>(
    this.onRowClick(e,idx)}
    度量={measure}
    datum={datum}
    索引={index}
    />
    )}
    );
    }
    render(){
    console.log('rendering..');
    返回(
    {({宽度,高度})=>(
    )}
    );
    }
    }
    导出默认值摘要;
    
    下面是表示一行的组件:

    import React, { Component } from 'react';
    import {
      Table,
      TableBody,
      TableHeader,
      TableHeaderColumn,
      TableRow,
      TableRowColumn,
    } from 'material-ui/Table';
    import { MuiThemeProvider } from 'material-ui/styles';
    import ExpansionPanel from '@material-ui/core/ExpansionPanel';
    import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary';
    import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails';
    import Typography from '@material-ui/core/Typography';
    
    
    class EquipSummaryRow extends Component {
      render() {
        const { datum } = this.props;
    
        return (
          <div>
            <ExpansionPanel
              defaultExpanded
              onChange={e => this.props.onClick(e, this.props.index)}
            >
              <ExpansionPanelSummary expandIcon={<div>|</div>}>
                <Typography>{`${datum.type}      (id: ${datum.instance}, points: ${datum.points.length})`}</Typography>
              </ExpansionPanelSummary>
              <ExpansionPanelDetails>
                <Table>
                  <TableHeader
                    displaySelectAll={false}
                    adjustForCheckbox={false}
                  >
                    <TableRow>
                      <TableHeaderColumn>Device</TableHeaderColumn>
                      <TableHeaderColumn>Object ID</TableHeaderColumn>
                      <TableHeaderColumn>Type</TableHeaderColumn>
                      <TableHeaderColumn>Name</TableHeaderColumn>
                      <TableHeaderColumn>Description</TableHeaderColumn>
                      <TableHeaderColumn>Units</TableHeaderColumn>
                      <TableHeaderColumn>Value</TableHeaderColumn>
                    </TableRow>
                  </TableHeader>
                  <TableBody
                    displayRowCheckbox={false}
                  >
                    {datum.points.map((row, index) => (
                      <TableRow key={row.id}>
                        <TableRowColumn>{row.device}</TableRowColumn>
                        <TableRowColumn>{row.objectID}</TableRowColumn>
                        <TableRowColumn>{row.type}</TableRowColumn>
                        <TableRowColumn>{row.name}</TableRowColumn>
                        <TableRowColumn>{row.description}</TableRowColumn>
                        <TableRowColumn>{row.units}</TableRowColumn>
                        <TableRowColumn>{row.value}</TableRowColumn>
                      </TableRow>
                      ))}
                  </TableBody>
                </Table>
              </ExpansionPanelDetails>
            </ExpansionPanel>
          </div>
        );
      }
    }
    
    export default EquipSummaryRow;
    
    import React,{Component}来自'React';
    进口{
    桌子
    表体,
    表格标题,
    TableHeaderColumn,
    TableRow,
    TableRowColumn,
    }来自“物料界面/表格”;
    从“材质ui/样式”导入{MuiThemeProvider};
    从“@material ui/core/ExpansionPanel”导入ExpansionPanel;
    从“@material ui/core/ExpansionPanelSummary”导入ExpansionPanelSummary;
    从“@material ui/core/ExpansionPanelDetails”导入ExpansionPanelDetails;
    从“@material ui/core/Typography”导入排版;
    类EquipmSummaryRow扩展组件{
    render(){
    const{datum}=this.props;
    返回(
    this.props.onClick(e,this.props.index)}
    >
    {${datum.type}(id:${datum.instance},points:${datum.points.length})`}
    装置
    对象ID
    类型
    名称
    描述
    单位
    价值
    {datum.points.map((行,索引)=>(
    {row.device}
    {row.objectID}
    {row.type}
    {row.name}
    {row.description}
    {row.units}
    {row.value}
    ))}
    );
    }
    }
    导出默认设备汇总行;
    

    这可能是我如何使用缓存的问题吗?我一直在为此绞尽脑汁,所以任何建议都很感激

    解决了我的问题。问题在于,“材质UI可展开”面板具有动画折叠,因此在面板到达其展开/折叠形式之间存在延迟。“onChange”事件立即激发,以便在动画发生时进行测量。我目前正试图找出一种在动画结束后触发测量的方法,但这不是react virtualized的问题。

    (这不是一个完整的答案,但它确实允许动画步骤按设计工作。如果有足够的时间,我认为这可以完全工作。有关更多信息,请参阅我最后的评论。)

    列表
    组件中,有一个选项可以传入不同的
    cellRangeRenderer
    。此
    cellRangeRenderer
    负责生成附加到每个单元格的
    样式
    对象。默认的
    cellRangeRenderer
    使用绝对定位来完成此操作。I'我创建了一个修改过的
    cellRangeRenderer
    ,它实际上没有在
    样式
    对象中设置任何有效的内容,而是设置了gene
    import React from 'react'
    
    /**
     * Default implementation of cellRangeRenderer used by Grid.
     * This renderer supports cell-caching while the user is scrolling.
     */
    
    export default function cellRangeRenderer({
      cellCache,
      cellRenderer,
      columnSizeAndPositionManager,
      columnStartIndex,
      columnStopIndex,
      deferredMeasurementCache,
      horizontalOffsetAdjustment,
      isScrolling,
      isScrollingOptOut,
      parent, // Grid (or List or Table)
      rowSizeAndPositionManager,
      rowStartIndex,
      rowStopIndex,
      styleCache,
      verticalOffsetAdjustment,
      visibleColumnIndices,
      visibleRowIndices,
    }) {
      const renderedCells = [];
    
      // Browsers have native size limits for elements (eg Chrome 33M pixels, IE 1.5M pixes).
      // User cannot scroll beyond these size limitations.
      // In order to work around this, ScalingCellSizeAndPositionManager compresses offsets.
      // We should never cache styles for compressed offsets though as this can lead to bugs.
      // See issue #576 for more.
      const areOffsetsAdjusted = columnSizeAndPositionManager.areOffsetsAdjusted() || rowSizeAndPositionManager.areOffsetsAdjusted();
    
      const canCacheStyle = !isScrolling && !areOffsetsAdjusted;
      let styledBuffer = false
      let bufferStyle, containerStyle
    
      for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) {
        const rowDatum = rowSizeAndPositionManager.getSizeAndPositionOfCell(rowIndex);
    
        for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) {
          const columnDatum = columnSizeAndPositionManager.getSizeAndPositionOfCell(columnIndex);
          const isVisible = columnIndex >= visibleColumnIndices.start && columnIndex <= visibleColumnIndices.stop && rowIndex >= visibleRowIndices.start && rowIndex <= visibleRowIndices.stop;
          const key = `${rowIndex}-${columnIndex}`;
          let style;
          // this is the part that bugs out when react-virtualized re-renders part of the what's-showing-now list, rather than the entire what's-showing-now list
          // I'm just grabbing the first cell and assuming it's coordinates are the top of the what's-showing-now list
          if (!styledBuffer) {
            styledBuffer = true
            bufferStyle = {
              position: 'absolute',
              top: 0,
              left: 0,
              height: rowDatum.offset + verticalOffsetAdjustment,
              width: columnDatum.offset + horizontalOffsetAdjustment,
            }
            containerStyle = {
              position: 'absolute',
              top: rowDatum.offset + verticalOffsetAdjustment,
              left: columnDatum.offset + horizontalOffsetAdjustment,
              height: 'auto',
              width: 'auto',
            }
          }
    
          // Cache style objects so shallow-compare doesn't re-render unnecessarily.
          if (canCacheStyle && styleCache[key]) {
            style = styleCache[key];
          } else if (deferredMeasurementCache && !deferredMeasurementCache.has(rowIndex, columnIndex)) {
          // In deferred mode, cells will be initially rendered before we know their size.
          // Don't interfere with CellMeasurer's measurements by setting an invalid size.
            // Position not-yet-measured cells at top/left 0,0,
            // And give them width/height of 'auto' so they can grow larger than the parent Grid if necessary.
            // Positioning them further to the right/bottom influences their measured size.
            style = {
              height: 'auto',
              left: 0,
              position: 'absolute',
              top: 0,
              width: 'auto'
            };
          } else {
            // I'd go with a completely empty object, but that breaks other parts of react-virtualized that rely, at least, on 'width' being defined
            style = {
              height: 'auto',
              width: 'auto',
            }
            styleCache[key] = style;
          }
    
          const cellRendererParams = {
            columnIndex,
            isScrolling,
            isVisible,
            key,
            parent,
            rowIndex,
            style
          };
    
          let renderedCell;
    
          // Avoid re-creating cells while scrolling.
          // This can lead to the same cell being created many times and can cause performance issues for "heavy" cells.
          // If a scroll is in progress- cache and reuse cells.
          // This cache will be thrown away once scrolling completes.
          // However if we are scaling scroll positions and sizes, we should also avoid caching.
          // This is because the offset changes slightly as scroll position changes and caching leads to stale values.
          // For more info refer to issue #395
          //
          // If isScrollingOptOut is specified, we always cache cells.
          // For more info refer to issue #1028
          if ((isScrollingOptOut || isScrolling) && !horizontalOffsetAdjustment && !verticalOffsetAdjustment) {
            if (!cellCache[key]) {
              cellCache[key] = cellRenderer(cellRendererParams);
            }
    
            renderedCell = cellCache[key];
    
            // If the user is no longer scrolling, don't cache cells.
            // This makes dynamic cell content difficult for users and would also lead to a heavier memory footprint.
          } else {
            renderedCell = cellRenderer(cellRendererParams);
          }
    
          if (renderedCell === null || renderedCell === false) {
            continue;
          }
    
          if (process.env.NODE_ENV !== 'production') {
            warnAboutMissingStyle(parent, renderedCell);
          }
    
          renderedCells.push(renderedCell);
        }
      }
    
      // This is where the new "magic" happens
      return [(
        <div id="0-buffer-at-the-top" key="0-buffer-at-the-top" style={bufferStyle} />
      ), (
        <div id="0-container-at-the-top" key="0-container-at-the-top" style={containerStyle}>
          {renderedCells}
        </div>
      )];
    }
    
    function warnAboutMissingStyle(parent, renderedCellParam) {
      let renderedCell = renderedCellParam
      if (process.env.NODE_ENV !== 'production') {
        if (renderedCell) {
          // If the direct child is a CellMeasurer, then we should check its child
          // See issue #611
          if (renderedCell.type && renderedCell.type.__internalCellMeasurerFlag) {
            renderedCell = renderedCell.props.children;
          }
    
          if (renderedCell && renderedCell.props && renderedCell.props.style === undefined && parent.__warnedAboutMissingStyle !== true) {
            parent.__warnedAboutMissingStyle = true;
    
            console.warn('Rendered cell should include style property for positioning.');
          }
        }
      }
    }