1. ホーム
  2. javascript

[解決済み] Reactによるビッグリストのパフォーマンス

2022-11-13 01:07:59

質問

Reactでフィルタリング可能なリストを実装している最中です。リストの構造は以下の画像の通りです。

PREMISE

ここでは、どのように機能するかを説明します。

  • 状態は最上位のコンポーネントである Search コンポーネントに存在します。
  • 状態は以下のように記述されます。
{
    visible : boolean,
    files : 配列。
    filtered : 配列。
    query : 文字列,
    currentlySelectedIndex : 整数
}

  • files はファイルパスを含む非常に大きな配列です(10000エントリーはもっともな数です)。
  • filtered は、ユーザーが少なくとも 2 文字を入力した後にフィルタリングされた配列です。これは派生データであり、ステートに保存することについての議論があることは承知していますが、この配列が必要なのは
  • currentlySelectedIndex で、これはフィルタリングされたリストから現在選択されている要素のインデックスです。

  • ユーザーが2文字以上の文字を Input コンポーネントに 2 文字以上入力すると、配列はフィルタリングされ、 フィルタリングされた配列の各エントリに対して Result コンポーネントがレンダリングされます。

  • それぞれの Result コンポーネントはクエリに部分的にマッチしたフルパスを表示し、 パスの部分マッチ部分はハイライト表示されます。たとえば、ユーザーが「le」と入力した場合の Result コンポーネントの DOM は、次のようになります。

    <li>this/is/a/fi<strong>le</strong>/path</li>

  • が表示されているときに、ユーザーが上下キーを押した場合 Input コンポーネントがフォーカスされているときに currentlySelectedIndex に基づいて変化します。 filtered の配列に基づいて変更されます。これにより Result コンポーネントが選択されたものとしてマークされ、再レンダリングが行われます。

問題点

当初、私は十分小さい配列でテストしました。 files でテストしてみましたが、すべてうまくいきました。

問題が発生したのは、私が files という配列を処理しなければならないときに問題が発生しました。入力に 2 文字を入力すると大きなリストが生成され、上下のキーを押してそれを移動すると、非常にラグが生じます。

当初、私は Result 要素のレンダリングごとに、その場でリストを作成していました。 Search コンポーネントのレンダリングごとに、その場でリストを作成していただけです。

results  = this.state.filtered.map(function(file, index) {
    var start, end, matchIndex, match = this.state.query;

     matchIndex = file.indexOf(match);
     start = file.slice(0, matchIndex);
     end = file.slice(matchIndex + match.length);

     return (
         <li onClick={this.handleListClick}
             data-path={file}
             className={(index === this.state.currentlySelected) ? "valid selected" : "valid"}
             key={file} >
             {start}
             <span className="marked">{match}</span>
             {end}
         </li>
     );
}.bind(this));

お分かりのように、毎回 currentlySelectedIndex が変わるたびに再レンダリングが発生し、そのたびにリストが再作成されていました。と思っていたのですが key の値を各 li 要素ごとに再レンダリングする必要はありません。 li 要素に className を変更したのですが、どうやらそうではなかったようです。

結局はクラスを定義して Result 要素に対してクラスを定義し、そこで各 Result 要素が以前に選択されていたかどうか、そして現在のユーザー入力に基づいて再描画すべきかどうかを明示的にチェックします。

var ResultItem = React.createClass({
    shouldComponentUpdate : function(nextProps) {
        if (nextProps.match !== this.props.match) {
            return true;
        } else {
            return (nextProps.selected !== this.props.selected);
        }
    },
    render : function() {
        return (
            <li onClick={this.props.handleListClick}
                data-path={this.props.file}
                className={
                    (this.props.selected) ? "valid selected" : "valid"
                }
                key={this.props.file} >
                {this.props.children}
            </li>
        );
    }
});

と、このようにリストが作成されました。

results = this.state.filtered.map(function(file, index) {
    var start, end, matchIndex, match = this.state.query, selected;

    matchIndex = file.indexOf(match);
    start = file.slice(0, matchIndex);
    end = file.slice(matchIndex + match.length);
    selected = (index === this.state.currentlySelected) ? true : false

    return (
        <ResultItem handleClick={this.handleListClick}
            data-path={file}
            selected={selected}
            key={file}
            match={match} >
            {start}
            <span className="marked">{match}</span>
            {end}
        </ResultItem>
    );
}.bind(this));
}

これにより、パフォーマンス をわずかに が改善されましたが、まだ十分ではありません。しかし、React の製品版でテストしたときは、まったくラグがなく、非常にスムーズに動作しました。

最重要項目

Reactの開発版と製品版でこのような顕著な乖離があるのは正常なのでしょうか?

Reactがリストを管理する方法について考えるとき、私は何か間違った理解/行動をしていますか?

14-11-2016更新

マイケル・ジャクソンのプレゼンテーションで、これによく似た問題に取り組んでいるものを見つけました。 https://youtu.be/7S8v8jfLb1Q?t=26m2s

この解決策はAskarovBeknarの提案したものと非常によく似ています。 答え で提案されたものと非常によく似ています。

14-4-2018を更新しました。

これはどうやら人気のある質問のようで、当初の質問から事態が進展しているため、仮想レイアウトを把握するために、上記リンク先の動画を見ることをお勧めしますが、同時に 仮想化されたリアクト というライブラリがあります。

どのように解決するのですか?

この質問に対する他の多くの回答と同様に、主な問題は、フィルタリングとキーイベントの処理を行いながら、DOM 内の非常に多くの要素をレンダリングすると、時間がかかるという事実にあります。

問題の原因となっているReactに関して本質的に間違ったことをしているわけではありませんが、パフォーマンスに関連する多くの問題のように、UIも責任の大きな割合を占めることがあります。

UIが効率性を考慮して設計されていない場合、Reactのようなパフォーマンスを重視して設計されたツールでさえも被害を受けることになります。

結果セットをフィルタリングすることは、@Koen が言及したように、素晴らしいスタートとなります。

私はこのアイデアで少し遊んで、この種の問題に取り組む方法を説明するサンプル アプリを作成しました。

これは決して production ready のコードではありませんが、コンセプトを十分に説明していますし、より堅牢にするために修正することもできます。自由にコードを見てみてください。)

react-large-list-example