我可能正在尝试做一些不受支持的事情,但我正在尝试使用react-virtualized的CellMeasurer和MultiGrid。我还使用ScrollSync
来检测用户何时向右滚动并显示/隐藏指示符。
有一点需要注意的是,我有一个标签控件来操纵哪些数据(包括行和列)。当数据发生变化时,我在redux商店中设置了一个标志,并使用它来重新测量我的单元格。
它与我的期望非常接近。我第一次进入新选项卡时,所有单元格都正确测量,但有两个例外。
1)第一列(1个固定列)重新测量,但左上角和左下角网格的宽度不会更新。这在新测量和默认大小之间留下了空隙。滚动后,这会自行修复 - 非常确定,因为我有ScrollSync。
2)列索引1永远不会小于默认宽度。这是第一个非固定列。 适用于更大的内容:
然后,主要问题是当我返回之前已经显示过的标签时。发生这种情况时,即使我的新数据标志仍然触发重新测量,上一个选项卡中存在的列的测量结果也会延续。我想我需要做一些清除缓存的事情,但到目前为止我的尝试导致所有列都达到默认宽度。是否有一定的CellMeasurerCache.clearAll
,MultiGrid.measureAllCells
和MultiGrid.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。这个例子显示了我解释的大部分内容。它也为行提供了比预期更多的高度(不能说明与我的其他实现有什么不同)
答案 0 :(得分:2)
第一个建议:不要使用ScrollSync
。只需直接使用onScroll
的{{1}}属性即可。我认为MultiGrid
对于这种情况来说是过度的。
第二个建议:如果可能,请避免使用ScrollSync
同时测量宽度和高度,因为这需要贪婪地测量整个CellMeasurer
以计算最大单元格在每列+行中。在你的Plnkr中记录了一个关于此事件的开发警告,但它被另一个日志记录掩埋了:
CellMeasurerCache应该只测量单元格的宽度或高度。您已配置CellMeasurerCache来测量它们。这将导致性能不佳。
不幸的是,为了解决你的问题,我相信你已经发现了Grid
和CellMeasurer
之间互动的一些缺陷。
编辑这些缺陷已在9.2.3版本中得到解决。请升级。 :)
答案 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;
}