5

効率的なニュース フィードを作成するために、react-virtualized ライブラリを使用しています。図書館はすごいです。WindowScroller、AutoSizer、および VirtualScroll コンポーネントを組み合わせて、無限のスクロール動作を実現しました。問題は、VirtualScroll の高さを手動で設定し、WindowScroller を使用しない場合、すべてのブラウザーでパフォーマンスが優れていることです。ただし、WindowScroller コンポーネントを追加すると、特に Firefox (v47.0) でパフォーマンスが大幅に低下します。ウィンドウのスクロールを使用できるように、これを最適化するにはどうすればよいですか?

これは、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