Как устранить неполадки регулировки высоты строки с помощью расширяемых строк с помощью виртуализированного списка реагирования? - PullRequest
0 голосов
/ 05 июня 2018

Я использую расширяемые панели (Material-UI) в строках с реагирующим виртуализированным списком, и у меня возникли проблемы с автоматической настройкой высоты.Я прочитал несколько сообщений SO и некоторые проблемы с динамической высотой строк на сайте, реагирующем на виртуализацию, но у меня есть особая проблема, когда кажется, что есть проблема «по одному», когда высота строки регулируется после расширения панели/collapsed.

Вот ожидаемое поведение:

  1. Панель строк развернута по умолчанию.
  2. Пользователь щелкает строку расширяемой панели.
  3. Панель строк сворачивается.
  4. Высота строки подстраивается под свертывание панели.

Вот фактическое поведение ДЛЯ ПЕРВОГО ЩЕЛЧКА :

  1. Панель строк расширенапо умолчанию.
  2. Пользователь щелкает расширяемую строку панели.
  3. Панель строк разрушается.
  4. Высота строки НЕ адаптируется к разрушению панели.
  5. ОДНАКО, при последующихщелчок высоты строки регулирует, но в «противоположном» состоянии, что приводит к несогласованности - то есть при повторном щелчке по панели строк, чтобы развернуть, высота строки корректируется по высоте строки, как если бы она была свернута, и vicи наоборот.Поэтому, когда панель свернута, после нее появляется куча пробелов, а когда она технически расширена, высота строки слишком мала, чтобы видеть содержимое.

Я не уверен, какую другую информацию включатькроме публикации кода и того, что 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 } 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;

Может ли это быть проблемой с использованием кеша?Я бился головой с этим, поэтому любые предложения приветствуются!

Ответы [ 2 ]

0 голосов
/ 30 мая 2019

(Это не полный ответ, но он позволяет шагу анимации работать так, как задумано. Учитывая достаточно времени, я думаю, что это может сработать полностью. Пожалуйста, смотрите мои комментарии в конце для получения дополнительной информации.)

В компоненте List есть возможность передать другой cellRangeRenderer.Это cellRangeRenderer - это то, что отвечает за генерацию объекта style, который присоединен к каждой отдельной ячейке.По умолчанию cellRangeRenderer использует абсолютное позиционирование для достижения этой цели.Я создал модифицированный cellRangeRenderer, который на самом деле ничего не устанавливает в объекте style, а скорее создает контейнер для ячеек.Контейнер использует абсолютное позиционирование, чтобы показать ячейки, где они должны быть относительно полосы прокрутки, но внутри контейнера каждая ячейка просто отображается как есть.

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.');
      }
    }
  }
}

Этот код начал свою жизнь каккопия того, что распространяется в пакете npm (чтобы немного обойти этап компиляции babel).Он имеет, по крайней мере, следующие проблемы:

  • Он должен использоваться в списке, а не в сетке.Сетка потребует, чтобы ячейки были правильно размещены в сетке (Grid Material-UI, а не Grid, не реагирующей виртуализированной), а не просто выброшены туда.быть вызванным для подразделов списка, а не для всего визуализируемого фрагмента (исправление этого было за пределами временного интервала, который мне был выделен для попытки выполнить это исправление).Этот новый cellRangeRenderer будет работать примерно на 90% корректно как есть, если бы эта проблема была решена.
  • Поскольку вы можете развернуть строку, а затем прокрутить, размер строки все еще нуждается в CellMeasurer для вычисления высоты.Поскольку я не применяю высоту к каждой отдельной ячейке, нам нужно пересчитать «верхнюю» высоту контейнера, используя высоты немного более умным способом.Это нарушается только в том случае, если вы полностью прокрутите раздел, в котором отображается расширенная панель.Возможно, что простого применения высоты к объекту style будет достаточно, но это не проверено.Изменить: Вам все равно придется отложить вызов на measure, как подразумевает ваш ответ.
  • Переход к определенной ячейке не был проверен и может работать, а может и не работать.
0 голосов
/ 06 июня 2018

Разобрался с моей проблемой.Проблема заключается в том, что расширяемая панель Material-UI имеет анимированные свертки, поэтому существует задержка между тем, когда панель достигает своей развернутой / свернутой формы.Событие onChange происходит немедленно, поэтому измерение выполняется во время анимации.В настоящее время я пытаюсь найти способ запустить измерение после завершения анимации, но это не проблема с реагирующей виртуализацией.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...