[解決済み] Reactによるビッグリストのパフォーマンス
質問
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.jsの配列の子要素のユニークキーを理解する
-
[解決済み] リストのリストからフラットなリストを作るには?
-
[解決済み] リスト内のアイテムのインデックスを検索する
-
[解決済み] リストが空かどうかを確認するにはどうすればよいですか?
-
[解決済み] Pythonのリストメソッドであるappendとextendの違いは何ですか?
-
[解決済み] 割り当て後にリストが予期せず変更されました。その理由と防止策を教えてください。
-
[解決済み] リストを均等な大きさの塊に分割するには?
-
[解決済み] リストの最後の要素を取得する方法
-
[解決済み] Reactルータを使ったプログラムによるナビゲーション
-
[解決済み] React JSX内のループ
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] JSのDateからDay名
-
[解決済み] <Enter>でjQuery UIダイアログを送信する
-
[解決済み] Angularjs - 現在の日付を表示する
-
[解決済み] 文字列のn番目の出現箇所を取得するには?
-
[解決済み] モバイルWeb HTML5フレームワークの選び方【終了しました
-
[解決済み] なぜ "use strict "はパフォーマンスを10倍向上させるのか?
-
[解決済み] Chart.jsを使ってドーナツチャートの中にテキストを追加するには?
-
[解決済み] jQueryの$という記号の意味は何ですか?
-
[解決済み] モデルフェッチ時に1をtrueに、0をfalseに変換する方法
-
[解決済み] JavaScriptとLuaの微妙な違い [終了しました]