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