我在具有反应虚拟化列表的行中使用可扩展面板(Material-UI)并且一直存在高度自动调整问题。我已经在反应虚拟化网站上阅读了几篇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 } 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;
这可能是我如何使用缓存的问题吗?我一直在打我的头,所以任何建议都值得赞赏!
答案 0 :(得分:1)
弄清楚我的问题。问题是Material-UI可扩展面板具有动画崩溃,因此在面板到达其展开/折叠形式之间存在延迟。 &#39; onChange&#39;事件立即触发,以便在动画发生时进行测量。我目前正试图找出一种在动画结束后触发测量的方法,但这不是反应虚拟化的问题。
答案 1 :(得分:0)
(这不是一个完整的答案,但是它确实允许动画步骤按设计进行。如果有足够的时间,我认为这可以完全进行。请参阅我的评论以获取更多信息。)>
在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编译步骤)开始。至少有以下问题:
cellRangeRenderer
可以按原样正确运行90%。
CellMeasurer
来计算高度。因为我没有将高度应用于每个单独的单元格,所以我们需要以一种稍微聪明的方式使用高度重新计算容器的“顶部”高度。仅当您完全滚动到显示扩展面板的部分时,此选项才会损坏。仅将高度应用于style
对象可能就足够了,但这未经测试。编辑:您仍然需要按照暗示的方式将呼叫延迟到measure
。