5

我正在使用 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 中记录了窗口滚动和块滚动的性能时间线:

4

1 回答 1

1

我认为这与 ReactsetState()调用有关。

WindowScroller监听window对象的滚动事件。这些发生在 React 的知识之外,因此setState()调用由ReactDefaultBatchingStrategy同步处理。Grid是一个 React 组件,因此它的 onScroll 事件发生在 React 的感知范围内。setState()作为结果发生的调用可以通过ReactUpdateQueue.

查看您共享的时间线,滚动事件的setState调用由. 但看看时间线——帧速率最差的地方,它的调用会立即传递给.GridReactUpdateQueueWindowScrollersetState()ReactDefaultBatchingStrategy

于 2016-07-29T17:41:41.540 回答