Jestem w trakcie wdrażania listy z możliwością filtrowania w React. Struktura listy jest taka, jak pokazano na poniższej ilustracji.
PRZESŁANKA
Oto opis tego, jak to powinno działać:
- Stan znajduje się w komponencie najwyższego poziomu,
Searchkomponencie. - Stan jest opisany następująco:
{
widoczne: boolean,
pliki: tablica,
filtrowane: tablica,
zapytanie: ciąg,
currentSelectedIndex: integer
}
filesto potencjalnie bardzo duża tablica zawierająca ścieżki do plików (10 000 wpisów to wiarygodna liczba).filteredto przefiltrowana tablica po wpisaniu przez użytkownika co najmniej 2 znaków. Wiem, że to dane pochodne i jako taki można by argumentować o przechowywaniu ich w stanie, ale jest to potrzebnecurrentlySelectedIndexktóry jest indeksem aktualnie wybranego elementu z przefiltrowanej listy.Użytkownik wpisuje więcej niż 2 litery do
Inputkomponentu, tablica jest filtrowana i dla każdego wpisu w filtrowanej tablicyResultjest renderowany komponentKażdy
Resultskładnik wyświetla pełną ścieżkę, która częściowo pasuje do zapytania, a część ścieżki częściowego dopasowania jest podświetlona. Na przykład DOM komponentu wynikowego, gdyby użytkownik wpisał „le”, wyglądałby tak:<li>this/is/a/fi<strong>le</strong>/path</li>- Jeśli użytkownik naciśnie klawisze w górę lub w dół, gdy
Inputskładnik jest aktywny,currentlySelectedIndexzmiany oparte nafilteredtablicy. Powoduje toResult, że komponent zgodny z indeksem zostanie oznaczony jako wybrany, co spowoduje ponowne renderowanie
PROBLEM
Początkowo testowałem to z wystarczająco małą tablicą files, używając rozwojowej wersji Reacta i wszystko działało dobrze.
Problem pojawił się, gdy miałem do czynienia z filestablicą liczącą aż 10000 wpisów. Wpisanie 2 liter na wejściu wygenerowałoby dużą listę, a kiedy naciskałem klawisze w górę iw dół, aby się po niej poruszać, byłoby bardzo opóźnione.
Na początku nie miałem zdefiniowanego komponentu dla Resultelementów i robiłem po prostu listę w locie, przy każdym renderowaniu Searchkomponentu, jako takiej:
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));
Jak widać, za każdym razem, gdy currentlySelectedIndexzmiana powodowała ponowne renderowanie, a lista byłaby odtwarzana za każdym razem. Pomyślałem, że skoro ustawiłem keywartość dla każdego lielementu, React uniknie ponownego renderowania każdego innego lielementu, który nie miał swojej classNamezmiany, ale najwyraźniej tak nie było.
Skończyło się na tym Result, że zdefiniowałem klasę dla elementów, w której wyraźnie sprawdza, czy każdy Resultelement powinien być ponownie renderowany na podstawie tego, czy został wcześniej wybrany i na podstawie aktualnych danych wejściowych użytkownika:
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>
);
}
});
A lista jest teraz tworzona jako taka:
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));
}
To sprawiło, że wydajność jest nieco lepsza, ale nadal nie jest wystarczająco dobra. Rzecz w tym, że testowałem na produkcyjnej wersji Reacta, wszystko działało gładko, bez żadnych opóźnień.
DOLNA LINIA
Czy taka zauważalna rozbieżność między wersjami deweloperskimi i produkcyjnymi Reacta jest normalna?
Czy rozumiem / robię coś złego, gdy myślę o tym, jak React zarządza listą?
AKTUALIZACJA 14-11-2016
Znalazłem tę prezentację Michaela Jacksona, w której podejmuje on problem bardzo podobny do tego: https://youtu.be/7S8v8jfLb1Q?t=26m2s
Rozwiązanie jest bardzo podobne do rozwiązania zaproponowanego w odpowiedzi AskarovBeknara poniżej
AKTUALIZACJA 14-4-2018
Ponieważ jest to najwyraźniej popularne pytanie, a od czasu zadania oryginalnego pytania sytuacja się rozwinęła, a zachęcam do obejrzenia wideo, do którego link znajduje się powyżej, w celu zapoznania się z wirtualnym układem, zachęcam również do korzystania z narzędzia React Virtualized biblioteka, jeśli nie chcesz wymyślać koła na nowo.



