【问题标题】:react-virtualized WindowScroller performance issuesreact-virtualized WindowScroller 性能问题
【发布时间】: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 中记录了窗口滚动和块滚动的性能时间表:

【问题讨论】:

  • 看起来你的 isScrolling 参数在错误的函数中(所以它永远不会是真的)。那应该来自WindowScroller(不是InfiniteLoader)。不确定这是否会对您的滚动性能产生很大影响,因为我不确定该参数的用途。
  • 您有没有机会指出我可以并排查看 2 并查看时间轴的地方?
  • @brianvaughn 感谢您注意到 isScrolling 来自错误的位置,但它并没有解决滚动问题。我将尝试创建一个 Plunker 来演示这个问题。
  • @brianvaughn 添加了性能记录(都是在 react 开发模式下记录的,所以这些都比优化的构建慢一些),希望这会有所帮助。很难把我的例子举给 Plunker,但如果它有所作为,我绝对会的。

标签: javascript reactjs infinite-scroll smooth-scrolling react-virtualized


【解决方案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 的选项,它做同样的事情。
猜你喜欢
  • 2019-02-17
  • 2017-04-26
  • 2017-04-20
  • 2018-04-18
  • 2018-10-23
  • 2017-03-23
  • 2019-12-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多