react-virtualized WindowScroller 性能问题
Posted
技术标签:
【中文标题】react-virtualized WindowScroller 性能问题【英文标题】:react-virtualized WindowScroller performance issues 【发布时间】:2016-12-03 02:53:20 【问题描述】:我正在使用 react-virtualized 库来创建高效的新闻提要。图书馆真棒。我结合了 WindowScroller、AutoSizer 和 VirtualScroll 组件来实现无限滚动行为。问题是当我手动设置 VirtualScroll 高度并且不使用 WindowScroller 时,所有浏览器的性能都很好。但是,当我添加 WindowScroller 组件时,性能会显着降低,尤其是在 Firefox (v47.0) 中。我该如何优化这一点,以便使用窗口滚动是可行的?
这是 News 组件,这里使用了 react-virtualized, 我有 2 种类型的列表项 - 标题项和简单项,标题项包含一组新闻的日期,因此它有点长。
import React, PropTypes, Component from 'react';
import Divider from 'material-ui/Divider';
import Subheader from 'material-ui/Subheader';
import Grid, Row, Col from 'react-flexbox-grid';
import NewsItem from '../NewsItem';
import styles from './styles.css';
import CircularProgress from 'material-ui/CircularProgress';
import Paper from 'material-ui/Paper';
import classNames from 'classnames';
import InfiniteLoader, WindowScroller, AutoSizer, VirtualScroll from 'react-virtualized';
import shallowCompare from 'react-addons-shallow-compare';
class News extends Component
componentDidMount()
this.props.onFetchPage(0);
shouldComponentUpdate(nextProps, nextState)
return shallowCompare(this, nextProps, nextState);
getRowHeight( index )
const elementHeight = 200;
const headerHeight = 78;
if (!this.isRowLoaded(index))
return elementHeight;
return this.props.articles[index].isHeader ?
headerHeight + elementHeight : elementHeight;
displayElement(article, isScrolling)
return (
<Paper
key=article.id
className=classNames(styles.newsItemContainer,
[styles.scrolling]: isScrolling
)
>
<NewsItem ...article />
<Divider />
</Paper>
);
isRowLoaded(index)
return !this.props.hasNextPage || index < this.props.articles.length;
renderRow(index, isScrolling)
if (!this.isRowLoaded(index))
return (
<div className=styles.spinnerContainer>
this.props.isFetching ? <CircularProgress /> : null
</div>
);
const isHeader, date, article = this.props.articles[index];
if (isHeader)
return (
<div>
<Subheader
key=date
className=styles.groupHeader
>
date
</Subheader>
this.displayElement(article, isScrolling)
</div>
);
return this.displayElement(article, isScrolling);
noRowsRenderer()
return (<p>No articles found</p>);
render()
const
articles,
onFetchPage,
pageNumber,
isFetching,
hasNextPage
= this.props;
const loadMoreRows = isFetching ?
() => :
() => onFetchPage(pageNumber + 1);
const rowCount = hasNextPage ? articles.length + 1 : articles.length;
return (
<Grid>
<Row>
<Col xs=12 sm=8 smOffset=2>
<InfiniteLoader
isRowLoaded=( index ) => this.isRowLoaded(index)
loadMoreRows=loadMoreRows
rowCount=rowCount
>
( onRowsRendered, registerChild, isScrolling ) => (
<WindowScroller>
( height, scrollTop ) => (
<AutoSizer disableHeight>
( width ) => (
<VirtualScroll
autoHeight
ref=registerChild
height=height
rowCount=rowCount
rowHeight=(...args) => this.getRowHeight(...args)
rowRenderer=( index ) => this.renderRow(index, isScrolling)
width=width
noRowsRenderer=this.noRowsRenderer
onRowsRendered=onRowsRendered
overscanRowCount=10
scrollTop=scrollTop
/>
)
</AutoSizer>
)
</WindowScroller>
)
</InfiniteLoader>
</Col>
</Row>
</Grid>
);
News.propTypes =
articles: PropTypes.array.isRequired,
onFetchPage: PropTypes.func.isRequired,
isFetching: PropTypes.bool.isRequired,
pageNumber: PropTypes.number.isRequired,
hasNextPage: PropTypes.bool.isRequired
;
export default News;
并且列表项是以下组件:
import React, PropTypes from 'react';
import styles from './styles.css';
import Row, Col from 'react-flexbox-grid';
import shallowCompare from 'react-addons-shallow-compare';
import pick from 'lodash/pick';
import NewsItemContent from '../NewsItemContent';
class NewsItem extends React.Component
shouldComponentUpdate(nextProps, nextState)
return shallowCompare(this, nextProps, nextState);
render()
const contentProps = pick(this.props, [
'title', 'description', 'seedUrl', 'seedCode', 'date'
]);
return (
<div
onClick=() => window.open(this.props.url, '_blank')
className=styles.newsItem
>
this.props.imageUrl ?
<Row>
<Col xs=3>
<div
role="presentation"
style= backgroundImage: `url($this.props.imageUrl)`
className=styles.previewImage
/>
</Col>
<Col xs=9>
<NewsItemContent ...contentProps />
</Col>
</Row> :
<Row>
<Col xs=12>
<NewsItemContent ...contentProps />
</Col>
</Row>
</div>
);
NewsItem.propTypes =
imageUrl: PropTypes.string,
description: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
date: PropTypes.object.isRequired,
seedUrl: PropTypes.string.isRequired,
seedCode: PropTypes.string.isRequired
;
export default NewsItem;
这里的NewsItemContent是一个简单的纯组件,没有任何逻辑,这里就不放了。
谢谢!
更新: 我已经在 Firefox 中记录了窗口滚动和块滚动的性能时间表:
Block scrolling Window scrolling【问题讨论】:
看起来你的isScrolling
参数在错误的函数中(所以它永远不会是真的)。那应该来自WindowScroller
(不是InfiniteLoader
)。不确定这是否会对您的滚动性能产生很大影响,因为我不确定该参数的用途。
您有没有机会指出我可以并排查看 2 并查看时间轴的地方?
@brianvaughn 感谢您注意到 isScrolling 来自错误的位置,但它并没有解决滚动问题。我将尝试创建一个 Plunker 来演示这个问题。
@brianvaughn 添加了性能记录(都是在 react 开发模式下记录的,所以这些都比优化的构建慢一些),希望这会有所帮助。很难把我的例子举给 Plunker,但如果它有所作为,我绝对会的。
【参考方案1】:
我认为这与 React setState()
调用有关。
WindowScroller
监听 window
对象的滚动事件。这些发生在 React 的知识之外,所以 setState()
调用由同步的 ReactDefaultBatchingStrategy
处理。 Grid
是一个 React 组件,因此它的 onScroll 事件发生在 React 的感知范围内。 setState()
作为结果发生的调用可以通过ReactUpdateQueue
更智能地进行批处理。
查看您共享的时间线,setState
调用 Grid
滚动事件被 ReactUpdateQueue
排队。但是看看WindowScroller
时间线——帧速率最差的地方,它的setState()
调用会立即传递给ReactDefaultBatchingStrategy
。
【讨论】:
老实说,我对 React 的这一面不是很熟悉。我会将我的回复添加为评论而不是“答案”,但格式很不稳定,长度限制太短。 感谢您查看它,据我了解,它无法真正优化。我尝试使用 react-infinite 实现相同的列表,因为它还可以选择对窗口滚动事件做出反应,并且所有浏览器中的帧速率都要好得多(仅考虑窗口滚动),这对我来说很奇怪。 有趣。因此,使用等效的WindowScroller
从 react-infinite 获得更好的性能?如果是这样,我将不得不检查他们的实施。
是的,他们有一个名为 useWindowAsScrollContainer
的选项,它做同样的事情。以上是关于react-virtualized WindowScroller 性能问题的主要内容,如果未能解决你的问题,请参考以下文章
react 分页器 基于react-virtualized组件的分页器
React-virtualized 中对 registerChild 的 CellMeasurer 支持
React-virtualized - 是不是可以在窗口调整大小时更新 rowHeights?
React-virtualized 动态高度列表呈现最初堆叠的所有内容