我正在使用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中记录了性能时间表:
  • Block scrolling
  • Window scrolling
  • 最佳答案

    我认为这与React setState()调用有关。
    WindowScroller侦听window对象的滚动事件。这些发生在React的知识之外,因此setState()调用由同步的ReactDefaultBatchingStrategy处理。 Grid是一个React组件,因此其onScroll事件发生在React的意识范围内。结果产生的setState()调用可以通过ReactUpdateQueue更智能地进行批处理。

    查看您共享的时间表,setState将对Grid滚动事件的ReactUpdateQueue调用排队。但是,从WindowScroller时间轴(帧速率最差的地方)来看,它的setState()调用会立即传递给ReactDefaultBatchingStrategy

    09-04 00:13