Zachowaj ostrożność podczas iteracji po tablicach !!
Jest powszechnym błędnym przekonaniem, że użycie indeksu elementu w tablicy jest akceptowalnym sposobem tłumienia błędu, który prawdopodobnie znasz:
Each child in an array should have a unique "key" prop.
Jednak w wielu przypadkach tak nie jest! Jest to anty-wzór, który w niektórych sytuacjach może prowadzić do niepożądanych zachowań .
Zrozumienie key
prop
React używa key
rekwizytu, aby zrozumieć relację elementu do elementu DOM, która jest następnie wykorzystywana w procesie uzgadniania . Dlatego bardzo ważne jest, aby klucz zawsze pozostawał unikalny , w przeciwnym razie istnieje duża szansa, że React pomieszać elementy i zmutować niewłaściwy. Ważne jest również, aby te klucze pozostały statyczne podczas wszystkich renderowań w celu zachowania najlepszej wydajności.
Biorąc to pod uwagę, nie zawsze trzeba stosować powyższe, pod warunkiem, że wiadomo, że tablica jest całkowicie statyczna. Jednak w miarę możliwości zaleca się stosowanie najlepszych praktyk.
Deweloper React powiedział w tym numerze GitHub :
- kluczem nie jest tak naprawdę wydajność, lecz tożsamość (co z kolei prowadzi do lepszej wydajności). losowo przypisywane i zmieniające się wartości nie są tożsamością
- Nie możemy realistycznie dostarczyć kluczy [automatycznie] bez wiedzy o sposobie modelowania danych. Sugerowałbym może użycie funkcji haszującej, jeśli nie masz identyfikatorów
- Mamy już klucze wewnętrzne, gdy korzystamy z tablic, ale są one indeksem w tablicy. Po wstawieniu nowego elementu klucze są nieprawidłowe.
Krótko mówiąc, key
powinno być:
- Unikalny - klucz nie może być identyczny z kluczem elementu rodzeństwa .
- Statyczny - Klucz nigdy nie powinien zmieniać się między renderami.
Korzystanie z key
prop
Zgodnie z powyższym wyjaśnieniem dokładnie przestudiuj poniższe próbki i spróbuj wdrożyć, w miarę możliwości, zalecane podejście.
Źle (potencjalnie)
<tbody>
{rows.map((row, i) => {
return <ObjectRow key={i} />;
})}
</tbody>
Jest to prawdopodobnie najczęstszy błąd występujący podczas iteracji nad tablicą w React. Takie podejście nie jest technicznie „złe” , jest po prostu… „niebezpieczne”, jeśli nie wiesz, co robisz. Jeśli iterujesz przez tablicę statyczną, jest to całkowicie poprawne podejście (np. Tablica linków w menu nawigacyjnym). Jeśli jednak dodajesz, usuwasz, zmieniasz kolejność lub filtrujesz elementy, musisz zachować ostrożność. Spójrz na to szczegółowe wyjaśnienie w oficjalnej dokumentacji.
class MyApp extends React.Component {
constructor() {
super();
this.state = {
arr: ["Item 1"]
}
}
click = () => {
this.setState({
arr: ['Item ' + (this.state.arr.length+1)].concat(this.state.arr),
});
}
render() {
return(
<div>
<button onClick={this.click}>Add</button>
<ul>
{this.state.arr.map(
(item, i) => <Item key={i} text={"Item " + i}>{item + " "}</Item>
)}
</ul>
</div>
);
}
}
const Item = (props) => {
return (
<li>
<label>{props.children}</label>
<input value={props.text} />
</li>
);
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
W tym fragmencie używamy niestatycznej tablicy i nie ograniczamy się do używania go jako stosu. To niebezpieczne podejście (zobaczysz dlaczego). Zauważ, jak dodając elementy na początku tablicy (w zasadzie cofnij przesunięcie), wartość każdego z nich <input>
pozostaje na miejscu. Dlaczego? Ponieważ key
nie jednoznacznie identyfikuje każdy element.
Innymi słowy, na początku Item 1
ma key={0}
. Kiedy dodamy drugi element, najwyższy element staje się Item 2
, a następnie Item 1
jako drugi element. Jednak teraz Item 1
ma key={1}
i key={0}
już nie ma. Zamiast tego Item 2
teraz ma key={0}
!!
React uważa, że <input>
elementy się nie zmieniły, ponieważ Item
klawisz z 0
jest zawsze na górze!
Dlaczego więc takie podejście jest czasami złe?
Takie podejście jest ryzykowne tylko wtedy, gdy tablica jest w jakiś sposób filtrowana, przestawiana lub elementy są dodawane / usuwane. Jeśli zawsze jest statyczny, to jest całkowicie bezpieczny w użyciu. Na przykład menu nawigacyjne, takie jak, ["Home", "Products", "Contact us"]
może być bezpiecznie iterowane za pomocą tej metody, ponieważ prawdopodobnie nigdy nie dodasz nowych łączy ani nie zmienisz ich położenia.
W skrócie, oto kiedy możesz bezpiecznie używać indeksu jakokey
:
- Tablica jest statyczna i nigdy się nie zmieni.
- Tablica nigdy nie jest filtrowana (wyświetla podzbiór tablicy).
- Tablica nigdy nie jest zmieniana.
- Tablica jest używana jako stos lub LIFO (ostatnie wejście, pierwsze wyjście). Innymi słowy, dodawanie można wykonać tylko na końcu tablicy (tj. Push) i tylko ostatni element można usunąć (np. Pop).
Gdybyśmy zamiast tego we fragmencie powyżej przesunęli dodany element na koniec tablicy, kolejność dla każdego istniejącego elementu zawsze byłaby poprawna.
Bardzo źle
<tbody>
{rows.map((row) => {
return <ObjectRow key={Math.random()} />;
})}
</tbody>
Chociaż takie podejście prawdopodobnie zagwarantuje unikalność kluczy, zawsze wymusi reakcję w celu ponownego renderowania każdego elementu na liście, nawet jeśli nie jest to wymagane. To bardzo złe rozwiązanie, ponieważ ma duży wpływ na wydajność. Nie wspominając już o tym, że nie można wykluczyć możliwości kolizji klucza w przypadku, Math.random()
gdy dwukrotnie otrzyma ten sam numer.
Niestabilne klucze (takie jak te produkowane przez Math.random()
) spowodują niepotrzebne odtworzenie wielu instancji komponentów i węzłów DOM, co może spowodować pogorszenie wydajności i utratę stanu komponentów potomnych.
Bardzo dobre
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uniqueId} />;
})}
</tbody>
Jest to prawdopodobnie najlepsze podejście, ponieważ wykorzystuje właściwość, która jest unikalna dla każdego elementu w zestawie danych. Na przykład, jeśli rows
zawiera dane pobrane z bazy danych, można użyć klucza podstawowego tabeli ( który zwykle jest liczbą automatycznie zwiększającą wartość ).
Najlepszym sposobem na wybranie klucza jest użycie ciągu, który jednoznacznie identyfikuje element listy wśród jego rodzeństwa. Najczęściej używasz identyfikatorów z danych jako kluczy
Dobrze
componentWillMount() {
let rows = this.props.rows.map(item => {
return {uid: SomeLibrary.generateUniqueID(), value: item};
});
}
...
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uid} />;
})}
</tbody>
To również dobre podejście. Jeśli zestaw danych nie zawiera żadnych danych gwarantujących unikalność ( np. Tablicę dowolnych liczb ), istnieje ryzyko kolizji klucza. W takich przypadkach najlepiej jest ręcznie wygenerować unikalny identyfikator dla każdego elementu w zestawie danych przed iteracją. Najlepiej podczas montowania komponentu lub odbierania zestawu danych ( np. Z props
lub z wywołania asynchronicznego interfejsu API ), aby zrobić to tylko raz , a nie za każdym razem, gdy komponent jest ponownie renderowany. Istnieje już kilka bibliotek, które mogą dostarczyć takie klucze. Oto jeden przykład: indeks klucza reakcji .
key
właściwość. Pomoże ReactJS znaleźć odniesienia do odpowiednich węzłów DOM i zaktualizować tylko zawartość wewnątrz narzutów, ale nie zrenderuje całej tabeli / wiersza.