react-virtualized:如何使用单独的 rowRenderer 进行高度测量
react-virtualized: how to use a separate rowRenderer for height measurement
我正在尝试使用 react-virtualized List
组件显示带有复杂标记的大型文本文档。该文档被分解成比某个最大值短的不同长度的文本块。 List
组件将这些块中的每一个呈现为一行。
由于块长度不同,我无法使用固定的行高,因此我想使用 CellMeasurer
。问题是在每个块上生成标记所需的解析非常昂贵——这是我想使用 react-virtualized 的部分原因。即使所有的块都在后台渲染,还是太慢了。
由于标记不影响高度,我想使用一个更简单的 rowRenderer
函数来呈现没有标记的文本 仅用于测量行 ,然后提供一个单独的和更完整的 rowRenderer
用于每个带有标记的块的实际渲染。有办法吗?
诚然,这 确实 很老套,不是最佳实践,但您可以这样做:
<List
rowHeight={(index) => {
const rowData = data[index];
let div = document.getElementById('renderer');
if (!div) {
div = document.createElement('div');
div.id = 'renderer';
}
ReactDOM.render(div, <Row>{rowData}</Row>);
const height = div.offsetHeight;
ReactDOM.unmountComponentAtNode(div);
if (index === data.length - 1) {
div.parentNode.removeChild(div);
}
return height;
}}
/>
或者,实际上,您可以有两个列表,一个带有 visibility: hidden
,您只需在其中渲染每一行而不带标记,获取高度,然后将其添加到数组中。一旦数组的长度等于你的数据长度,你就不再显示它,然后渲染另一个,用rowHeight={index => heights[index]}
经过反复试验,我发现一个很好的方法是让 rowRenderer
函数决定以哪种方式呈现行。您可以通过检查列表中 CellMeasurerCache
实例使用中的 _rowHeightCache
属性 来执行此操作。请注意,_rowHeightCache
的键采用以下形式:“index
-0”,其中 index
是行的索引。
以下是设置行渲染器的方法:
TextRowRenderer(
{
key, // Unique key within array of rows
index, // Index of row within collection
// isScrolling, // The List is currently being scrolled
style, // Style object to be applied to row (to position it)
parent, // reference to List
},
) {
const { textArray } = this.props;
// get the cached row height for this row
const rowCache = this.listCache._rowHeightCache[`${index}-0`];
// if it has been cached, render it using the normal, more expensive
// version of the component, without bothering to wrap it in a
// CellMeasurer (it's already been measured!)
// Note that listCache.defaultHeight has been set to 0, to make the
// the comparison easy
if (rowCache !== null && rowCache !== undefined && rowCache !== 0) {
return (
<TextChunk
key={key}
chunk={textArray[index]}
index={index}
style={style}
/>
);
}
// If the row height has not been cached (meaning it has not been
// measured, return the text chunk component, but this time:
// a) it's wrapped in CellMeasurer, which is configured with the
// the cache, and
// b) it receives the prop textOnly={true}, which it tells it to
// to skip rendering the markup
return (
<CellMeasurer
cache={this.listCache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
{() => {
return (
<TextChunk
key={key}
chunk={textArray[index]}
index={index}
style={style}
textOnly
/>
);
}}
</CellMeasurer>
);
}
然后以普通方式传递给 List
组件:
<List
height={pageHeight}
width={pageWidth}
rowCount={textArray.length}
rowHeight={this.listCache.rowHeight}
rowRenderer={this.TextRowRenderer}
deferredMeasurementCache={this.listCache}
style={{ outline: 'none' }}
ref={(r) => { this.listRef = r; }}
/>
通过包含一个参考回调,我们现在可以访问
measureAllRows
方法,我们可以用它来强制 List
提前渲染所有行以获得高度。这会
确保滚动条功能正常,但即使
textOnly
标志可能需要更长的时间。就我而言,我相信
值得等待。
以下是调用 measureAllRows
的方法:
componentDidUpdate() {
const {
textArray,
} = this.props;
// We will only run measureAllRows if they have not been
// measured yet. An easy way to check is to see if the
// last row is measured
const lastRowCache = this
.listCache
._rowHeightCache[`${textArray.length - 1}-0`];
if (this.listRef
|| lastRowCache === null
|| lastRowCache === undefined
|| lastRowCache === 0) {
try {
this.listRef.measureAllRows();
} catch {
console.log('failed to measure all rows');
}
}
}
try-catch 块是必需的,因为如果它试图测量
在构建 List
之前,它会抛出一个 index-out-of
-边界错误。
最后的想法
另一种可能的方法是让实际的 List
组件以缓存的测量为条件,而不是
rowRenderer
函数。您可以将文本数组呈现为
常规列表,将每一行包装在 <CellMeasurer>
中。当。。。的时候
缓存已满,然后您将渲染一个虚拟 List
配置
使用预填充缓存。这将避免调用
measureAllRows
在 componentDidUpdate
或 useEffect
回调中。
我正在尝试使用 react-virtualized List
组件显示带有复杂标记的大型文本文档。该文档被分解成比某个最大值短的不同长度的文本块。 List
组件将这些块中的每一个呈现为一行。
由于块长度不同,我无法使用固定的行高,因此我想使用 CellMeasurer
。问题是在每个块上生成标记所需的解析非常昂贵——这是我想使用 react-virtualized 的部分原因。即使所有的块都在后台渲染,还是太慢了。
由于标记不影响高度,我想使用一个更简单的 rowRenderer
函数来呈现没有标记的文本 仅用于测量行 ,然后提供一个单独的和更完整的 rowRenderer
用于每个带有标记的块的实际渲染。有办法吗?
诚然,这 确实 很老套,不是最佳实践,但您可以这样做:
<List
rowHeight={(index) => {
const rowData = data[index];
let div = document.getElementById('renderer');
if (!div) {
div = document.createElement('div');
div.id = 'renderer';
}
ReactDOM.render(div, <Row>{rowData}</Row>);
const height = div.offsetHeight;
ReactDOM.unmountComponentAtNode(div);
if (index === data.length - 1) {
div.parentNode.removeChild(div);
}
return height;
}}
/>
或者,实际上,您可以有两个列表,一个带有 visibility: hidden
,您只需在其中渲染每一行而不带标记,获取高度,然后将其添加到数组中。一旦数组的长度等于你的数据长度,你就不再显示它,然后渲染另一个,用rowHeight={index => heights[index]}
经过反复试验,我发现一个很好的方法是让 rowRenderer
函数决定以哪种方式呈现行。您可以通过检查列表中 CellMeasurerCache
实例使用中的 _rowHeightCache
属性 来执行此操作。请注意,_rowHeightCache
的键采用以下形式:“index
-0”,其中 index
是行的索引。
以下是设置行渲染器的方法:
TextRowRenderer(
{
key, // Unique key within array of rows
index, // Index of row within collection
// isScrolling, // The List is currently being scrolled
style, // Style object to be applied to row (to position it)
parent, // reference to List
},
) {
const { textArray } = this.props;
// get the cached row height for this row
const rowCache = this.listCache._rowHeightCache[`${index}-0`];
// if it has been cached, render it using the normal, more expensive
// version of the component, without bothering to wrap it in a
// CellMeasurer (it's already been measured!)
// Note that listCache.defaultHeight has been set to 0, to make the
// the comparison easy
if (rowCache !== null && rowCache !== undefined && rowCache !== 0) {
return (
<TextChunk
key={key}
chunk={textArray[index]}
index={index}
style={style}
/>
);
}
// If the row height has not been cached (meaning it has not been
// measured, return the text chunk component, but this time:
// a) it's wrapped in CellMeasurer, which is configured with the
// the cache, and
// b) it receives the prop textOnly={true}, which it tells it to
// to skip rendering the markup
return (
<CellMeasurer
cache={this.listCache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
{() => {
return (
<TextChunk
key={key}
chunk={textArray[index]}
index={index}
style={style}
textOnly
/>
);
}}
</CellMeasurer>
);
}
然后以普通方式传递给 List
组件:
<List
height={pageHeight}
width={pageWidth}
rowCount={textArray.length}
rowHeight={this.listCache.rowHeight}
rowRenderer={this.TextRowRenderer}
deferredMeasurementCache={this.listCache}
style={{ outline: 'none' }}
ref={(r) => { this.listRef = r; }}
/>
通过包含一个参考回调,我们现在可以访问
measureAllRows
方法,我们可以用它来强制 List
提前渲染所有行以获得高度。这会
确保滚动条功能正常,但即使
textOnly
标志可能需要更长的时间。就我而言,我相信
值得等待。
以下是调用 measureAllRows
的方法:
componentDidUpdate() {
const {
textArray,
} = this.props;
// We will only run measureAllRows if they have not been
// measured yet. An easy way to check is to see if the
// last row is measured
const lastRowCache = this
.listCache
._rowHeightCache[`${textArray.length - 1}-0`];
if (this.listRef
|| lastRowCache === null
|| lastRowCache === undefined
|| lastRowCache === 0) {
try {
this.listRef.measureAllRows();
} catch {
console.log('failed to measure all rows');
}
}
}
try-catch 块是必需的,因为如果它试图测量
在构建 List
之前,它会抛出一个 index-out-of
-边界错误。
最后的想法
另一种可能的方法是让实际的 List
组件以缓存的测量为条件,而不是
rowRenderer
函数。您可以将文本数组呈现为
常规列表,将每一行包装在 <CellMeasurer>
中。当。。。的时候
缓存已满,然后您将渲染一个虚拟 List
配置
使用预填充缓存。这将避免调用
measureAllRows
在 componentDidUpdate
或 useEffect
回调中。