我正在使用react-virtualized库创建高效的新闻提要。图书馆很棒。我结合了WindowScroller,AutoSizer和VirtualScroll组件来具有无限的滚动行为。问题是,当我手动设置VirtualScroll高度并且不使用WindowScroller时,在所有浏览器中的性能都很好。但是,当我添加WindowScroller组件时,性能会大大降低,尤其是在Firefox(v47.0)中。如何优化此方法,以便可以使用窗口滚动?
这是新闻组件,其中使用了react-virtualized,
我有2种类型的列表项- header 项和简单项, header 项包含一组新闻的日期,因此要更长一些。
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中记录了性能时间表:
最佳答案
我认为这与React setState()
调用有关。WindowScroller
侦听window
对象的滚动事件。这些发生在React的知识之外,因此setState()
调用由同步的ReactDefaultBatchingStrategy
处理。 Grid
是一个React组件,因此其onScroll事件发生在React的意识范围内。结果产生的setState()
调用可以通过ReactUpdateQueue
更智能地进行批处理。
查看您共享的时间表,setState
将对Grid
滚动事件的ReactUpdateQueue
调用排队。但是,从WindowScroller
时间轴(帧速率最差的地方)来看,它的setState()
调用会立即传递给ReactDefaultBatchingStrategy
。