Renderowanie list

Często zdarzy ci się wyświetlać wiele podobnych komponentów na podstawie kolekcji danych. Możesz użyć javascriptowych metod tablicowych, aby manipulować tablicą danych. Będziesz tu korzystać z filter() i map() w połączeniu z Reactem, aby przefiltrować i przekształcić tablicę danych w tablicę komponentów.

W tej sekcji dowiesz się

  • Jak renderować komponenty z tablicy za pomocą javascriptowej funkcji map().
  • Jak renderować tylko konkretne komponenty za pomocą javascriptowej funkcji filter().
  • Kiedy i dlaczego stosować klucze (ang. keys) w Reakcie.

Renderowanie list z tablic

Załóżmy, że posiadasz pewną listę treści.

<ul>
<li>Creola Katherine Johnson: matematyczka</li>
<li>Mario José Molina-Pasquel Henríquez: chemik</li>
<li>Mohammad Abdus Salam: fizyk</li>
<li>Percy Lavon Julian: chemik</li>
<li>Subrahmanyan Chandrasekhar: astrofizyk</li>
</ul>

Jedyna różnica między elementami tej listy to ich treść, ich dane. Często będziesz chcieć pokazać kilka instancji tego samego komponentu, używając różnych danych podczas tworzenia interfejsów: od list komentarzy do galerii obrazków profilowych. W takich sytuacjach możesz przechowywać te dane w obiektach i tablicach javascriptowych oraz używać metod, takich jak map() i filter(), aby renderować na ich podstawie listy komponentów.

Oto krótki przykład, jak generować listę elementów z tablicy:

  1. Przenieś dane do tablicy:
const people = [
'Creola Katherine Johnson: matematyczka',
'Mario José Molina-Pasquel Henríquez: chemik',
'Mohammad Abdus Salam: fizyk',
'Percy Lavon Julian: chemik',
'Subrahmanyan Chandrasekhar: astrofizyk'
];
  1. Zmapuj elementy tablicy people na nową tablicę węzłów JSX, listItems:
const listItems = people.map(person => <li>{person}</li>);
  1. Zwróć tablicę listItems z twojego komponentu, opakowaną w element <ul>:
return <ul>{listItems}</ul>;

Oto rezultat:

const people = [
  'Creola Katherine Johnson: matematyczka',
  'Mario José Molina-Pasquel Henríquez: chemik',
  'Mohammad Abdus Salam: fizyk',
  'Percy Lavon Julian: chemik',
  'Subrahmanyan Chandrasekhar: astrofizyk'
];

export default function List() {
  const listItems = people.map(person =>
    <li>{person}</li>
  );
  return <ul>{listItems}</ul>;
}

Zauważ, że powyższy sandbox wyświetla błąd w konsoli:

Konsola
Warning: Each child in a list should have a unique “key” prop.

Poniżej na tej stronie dowiesz się, jak naprawić ten błąd. Zanim do tego przejdziemy, nadajmy trochę struktury twoim danym.

Filtrowanie tablicy elementów

Dane te można jeszcze bardziej ustrukturyzować.

const people = [{
id: 0,
name: 'Creola Katherine Johnson',
profession: 'matematyczka',
}, {
id: 1,
name: 'Mario José Molina-Pasquel Henríquez',
profession: 'chemik',
}, {
id: 2,
name: 'Mohammad Abdus Salam',
profession: 'fizyk',
}, {
name: 'Percy Lavon Julian',
profession: 'chemik',
}, {
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrofizyk',
}];

Załóżmy, że chcesz mieć możliwość pokazywania tylko tych osób, których zawód to 'chemik'. Możesz skorzystać z javascriptowej metody filter(), aby zwrócić tylko takie osoby. Metoda ta przyjmuje tablicę, poddaje jej elementy “testowi” (funkcji, która zwraca true lub false) i zwraca nową tablicę tylko tych elementów, które zdały test (zwróciły true).

Chcąc uzyskać tylko te elementy, gdzie profession jest ustawione na 'chemik', odpowiednia funkcja “testu” powinna wyglądać tak: (person) => person.profession === 'chemik'. Oto sposób na to, jak to wszystko połączyć w całość:

  1. Utwórz nową tablicę zawierającą tylko osoby o zawodzie 'chemik' i nazwij ją chemists. Wywołaj metodę filter() na tablicy people, filtrując według warunku person.profession === 'chemik' i przypisz jej rezultat do nowo utworzonej tablicy:
const chemists = people.filter(person =>
person.profession === 'chemik'
);
  1. Teraz zmapuj tablicę chemists:
const listItems = chemists.map(person =>
<li>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
{person.accomplishment}.
</p>
</li>
);
  1. Wreszcie zwróć listItems z twojego komponentu:
return <ul>{listItems}</ul>;
import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const chemists = people.filter(person =>
    person.profession === 'chemik'
  );
  const listItems = chemists.map(person =>
    <li>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        {person.accomplishment}.
      </p>
    </li>
  );
  return <ul>{listItems}</ul>;
}

Zwróć uwagę

Funkcje strzałkowe (ang. arrow function) niejawnie zwracają wyrażenie znajdujące się zaraz po =>, więc nie musisz używać instrukcji return:

const listItems = chemists.map(person =>
<li>...</li> // Niejawne zwrócenie!
);

Natomiast musisz wprost napisać return, jeśli po twoim => znajduje się nawias klamrowy!

const listItems = chemists.map(person => { // Nawias klamrowy
return <li>...</li>;
});

Funkcje strzałkowe zawierające => { są określane jako posiadające “ciało blokowe”. Pozwalają one na napisanie więcej niż jednej linii kodu, ale zawsze musisz samodzielnie napisać instrukcję return. Jeśli to przeoczysz, nic nie zostanie zwrócone!

Zachowanie kolejności elementów listy za pomocą key (ang. klucz)

Zauważ, że wszystkie powyższe sandboxy wyświetlają błąd w konsoli:

Konsola
Warning: Each child in a list should have a unique “key” prop.

Każdemu elementowi tablicy musisz przypisać klucz key, czyli łańcuch znaków lub liczbę, która jednoznacznie identyfikuje go wśród innych elementów w tej tablicy:

<li key={person.id}>...</li>

Notatka

Elementy JSX bezpośrednio wewnątrz wywołania funkcji map() muszą zawsze mieć przypisane klucze!

Klucze key pozwalają Reactowi zrozumieć, który komponent odpowiada któremu elementowi tablicy, dzięki czemu może je później dopasować. Jest to istotne, jeśli elementy tablicy mogą być przemieszczane (np. w wyniku sortowania), dodawane lub usuwane. Dobrze dobrany klucz key pomaga Reactowi wywnioskować, co dokładnie się stało, i dokonać odpowiednich aktualizacji drzewa DOM.

Zamiast generować klucze dynamicznie, powinno się dołączać je do danych:

export const people = [{
  id: 0, // Użyte w JSX jako klucz
  name: 'Creola Katherine Johnson',
  profession: 'matematyczka',
  accomplishment: 'znana z obliczeń związanych z lotami kosmicznymi',
  imageId: 'MK3eW3A'
}, {
  id: 1, // Użyte w JSX jako klucz
  name: 'Mario José Molina-Pasquel Henríquez',
  profession: 'chemik',
  accomplishment: 'znany z odkrycia dziury ozonowej nad Arktyką',
  imageId: 'mynHUSa'
}, {
  id: 2, // Użyte w JSX jako klucz
  name: 'Mohammad Abdus Salam',
  profession: 'fizyk',
  accomplishment: 'znany z prac nad teorią elektromagnetyzmu',
  imageId: 'bE7W1ji'
}, {
  id: 3, // Użyte w JSX jako klucz
  name: 'Percy Lavon Julian',
  profession: 'chemik',
  accomplishment: 'znany z pionierskich prac nad lekami na bazie kortyzonu, steroidami i pigułkami antykoncepcyjnymi',
  imageId: 'IOjWm71'
}, {
  id: 4, // Użyte w JSX jako klucz
  name: 'Subrahmanyan Chandrasekhar',
  profession: 'astrofizyk',
  accomplishment: 'znany z obliczeń masy białych karłów',
  imageId: 'lrWQx8l'
}];

Dla dociekliwych

Wyświetlanie kilku węzłów drzewa DOM dla każdego elementu listy

Co zrobić, gdy każdy element musi renderować nie jeden, ale kilka węzłów drzewa DOM?

Krótka składnia <>...</> Fragment nie pozwala na przekazanie klucza, więc musisz albo zgrupować je w pojedynczy <div>, albo użyć nieco dłuższej i bardziej jawnej składni <Fragment>:

import { Fragment } from 'react';

// ...

const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);

Fragmenty nie pojawiają się w drzewie DOM, więc użycie ich spowoduje uzyskanie płaskiej listy elementów <h1>, <p>, <h1>, <p> i tak dalej.

Skąd wziąć klucze key

Różne źródła danych dostarczają różnych kluczy:

  • Dane z bazy danych: Jeśli twoje dane pochodzą z bazy danych, możesz używać kluczy lub ID z tej bazy danych, które z natury są unikalne.
  • Lokalnie generowane dane: Jeśli twoje dane są generowane i przechowywane lokalnie (np. notatki w aplikacji do robienia notatek), użyj licznika przyrostowego crypto.randomUUID() lub paczki takiej jak uuid podczas tworzenia elementów.

Zasady kluczy

  • Klucze muszą być unikalne między rodzeństwem. Jednakże używanie tych samych kluczy dla węzłów JSX w różnych tablicach jest jak najbardziej w porządku.
  • Klucze nie mogą się zmieniać, bo to przeczy ich celowi! Nie generuj ich podczas renderowania.

Dlaczego React potrzebuje kluczy?

Wyobraź sobie, że pliki na twoim pulpicie nie mają nazw. Zamiast tego trzeba odwoływać się do nich przez ich kolejność - pierwszy plik, drugi plik i tak dalej. Można się do tego przyzwyczaić, ale gdyby usunąć plik, zaczęłoby być to kłopotliwe. Drugi plik stałby się pierwszym plikiem, trzeci plik byłby drugim plikiem i tak dalej.

Nazwy plików w folderze i klucze JSX w tablicy pełnią podobną rolę. Pozwalają nam jednoznacznie identyfikować element pośród swojego rodzeństwa. Dobrze dobrany klucz dostarcza więcej informacji niż pozycja w tablicy. Nawet jeśli pozycja zmieni się ze względu na ponowne sortowanie, klucz pozwala Reactowi identyfikować element przez cały cykl jego życia.

Zwróć uwagę

Możesz skusić się, aby użyć indeksu elementu w tablicy jako jego klucza. W rzeczywistości to właśnie jego użyje React, jeśli w ogóle nie określisz klucza. Jednak kolejność renderowania elementów będzie się zmieniać w miarę czasu, gdy jakiś element zostanie dodany, usunięty lub jeśli tablica zostanie posortowana. Indeks jako klucz często prowadzi do mało oczywistych i mylących błędów.

Podobnie nie generuj kluczy dynamicznie, na przykład za pomocą key={Math.random()}. Spowoduje to, że klucze nigdy nie będą się zgadzać między renderowaniami, co poskutkuje za każdym razem tworzeniem od nowa wszystkich komponentów i drzewa DOM. Nie tylko będzie to wolne, ale również sprawi, że utracisz wszystkie dane wprowadzone przez użytkownika wewnątrz elementów listy. Zamiast tego użyj stałego identyfikatora opartego na danych.

Zauważ, że twoje komponenty nie otrzymają key przez właściwości. Jest on używany jedynie jako wskazówka dla samego Reacta. Jeśli twój komponent potrzebuje identyfikatora, musisz przekazać go jako oddzielną właściwość: <Profile key={id} userId={id} />.

Powtórka

Na tej stronie nauczyliśmy cię:

  • Jak przenieść dane z komponentów do struktur danych, takich jak tablice i obiekty.
  • Jak generować zbiory podobnych komponentów za pomocą javascriptowej funkcji map().
  • Jak tworzyć tablice przefiltrowanych elementów za pomocą javascriptowej funkcji filter().
  • Jak i dlaczego ustawiać klucz key dla każdego komponentu w kolekcji, aby React mógł śledzić każdy z nich, nawet jeśli zmienią się ich pozycja lub dane.

Wyzwanie 1 z 4:
Dzielenie listy na dwie

Ten przykład wyświetla listę wszystkich osób.

Zmień go tak, aby pokazywał dwie oddzielne listy jedna po drugiej: Chemia i Wszyscy Inni. Tak jak wcześniej, możesz określić, czy osoba jest związana z chemią, sprawdzając warunek person.profession === 'chemist'.

import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const listItems = people.map(person =>
    <li key={person.id}>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        {person.accomplishment}.
      </p>
    </li>
  );
  return (
    <article>
      <h1>Naukowcy</h1>
      <ul>{listItems}</ul>
    </article>
  );
}