私はReactでフィルター可能なリストを実装しているところです。リストの構造は下の画像のようになっています。
前提
これがどのように機能するかについての説明です:
- 状態は、最上位のコンポーネントである
Search
コンポーネントに存在します。 - 状態は次のように記述されます。
{{ 表示:ブール値、 ファイル:配列、 フィルタリング:配列、 クエリ文字列、 currentSelectedIndex:整数 }
files
は、ファイルパスを含む潜在的に非常に大きな配列です(10000エントリが妥当な数です)。filtered
ユーザーが少なくとも2文字を入力した後、フィルター処理された配列です。私はそれが派生データであることを知っているので、それを状態に保存することについて議論することができますが、それはcurrentlySelectedIndex
これは、フィルタリングされたリストから現在選択されている要素のインデックスです。ユーザーが
Input
コンポーネントに2文字以上を入力すると、配列がフィルタリングされ、フィルタリングされた配列のエントリごとにResult
コンポーネントがレンダリングされます。各
Result
コンポーネントは、クエリに部分的に一致した完全なパスを表示しており、パスの部分的に一致した部分が強調表示されています。たとえば、結果コンポーネントのDOMは、ユーザーが「le」と入力した場合、次のようになります。<li>this/is/a/fi<strong>le</strong>/path</li>
Input
コンポーネントがフォーカスされているときにユーザーが上キーまたは下キーを押すcurrentlySelectedIndex
と、filtered
配列に基づいて変更が行われます。これにより、Result
インデックスに一致するコンポーネントが選択済みとしてマークされ、再レンダリングが発生します。
問題
最初はfiles
、Reactの開発バージョンを使用して、十分に小さい配列でこれをテストしましたが、すべて正常に機能しました。
この問題は、files
10000エントリもの大きな配列を処理する必要があるときに発生しました。入力に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
要素に値を設定したので、Reactは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がリストをどのように管理するかを考えるとき、私は何か間違ったことを理解/実行していますか?
2016年11月14日更新
マイケルジャクソンのこのプレゼンテーションを見つけました。彼はこれと非常によく似た問題に取り組んでいます:https://youtu.be/7S8v8jfLb1Q?t = 26m2s
解決策は、以下のAskarovBeknarの回答によって提案されたものと非常に似ています。
更新14-4-2018
これは明らかに人気のある質問であり、元の質問から状況が進んでいるため、上記のリンク先のビデオをご覧になることをお勧めしますが、仮想レイアウトを把握するために、ReactVirtualizedを使用することもお勧めします。車輪の再発明をしたくない場合はライブラリ。