Рендеринг списків

Часто потрібно показати кілька подібних компонентів із колекції даних. Ви можете використовувати методи JavaScript для масивів, щоб маніпулювати масивом даних. На цій сторінці ви використовуватиме filter() і map() разом з React для фільтрування та перетворення вашого масиву даних у масив компонентів.

Ви дізнаєтесь

  • Як рендерити компоненти з масиву, використовуючи map() з JavaScript
  • Як рендерити лиш деякі компоненти, використовуючи filter() з JavaScript
  • Коли і навіщо використовувати ключі React

Рендеринг масивів даних

Припустимо, у вас є список певних даних.

<ul>
<li>Кетрін Джонсон (Creola Katherine Johnson): математик</li>
<li>Маріо Моліна (Mario José Molina-Pasquel Henríquez): хімік</li>
<li>Абдус Салам (Moшинкаmad Abdus Salam): фізик</li>
<li>Персі Джуліан (Percy Lavon Julian): хімік</li>
<li>Субрахманьян Чандрасекар (Subrahmanyan Chandrasekhar): астрофізик</li>
</ul>

Єдина відмінність між цими елементами списку полягає у їхньому вмісті, їхніх даних. Під час побудови інтерфейсів вам часто буде потрібно відобразити кілька екземплярів одного компонента, використовуючи різні дані — від списків коментарів до галерей зображень профілів. У цьому разі ви можете зберігати ці дані в об’єктах і масивах JavaScript та використовувати методи як map() і filter(), щоб відрендерити з них списки компонентів.

Ось короткий приклад того, як згенерувати список елементів із масиву:

  1. Перенесіть дані у масив:
const people = [
'Кетрін Джонсон (Creola Katherine Johnson): математик',
'Маріо Моліна (Mario José Molina-Pasquel Henríquez): хімік',
'Абдус Салам (Moшинкаmad Abdus Salam): фізик',
'Персі Джуліан (Percy Lavon Julian): хімік',
'Субрахманьян Чандрасекар (Subrahmanyan Chandrasekhar): астрофізик'
];
  1. Перетворіть члени масиву people у новий масив JSX-вузлів — listItems:
const listItems = people.map(person => <li>{person}</li>);
  1. Поверніть listItems, обгорнутий в <ul>, з вашого компонента:
return <ul>{listItems}</ul>;

Ось результат:

const people = [
  'Кетрін Джонсон (Creola Katherine Johnson): математик',
  'Маріо Моліна (Mario José Molina-Pasquel Henríquez): хімік',
  'Абдус Салам (Moшинкаmad Abdus Salam): фізик',
  'Персі Джуліан (Percy Lavon Julian): хімік',
  'Субрахманьян Чандрасекар (Subrahmanyan Chandrasekhar): астрофізик'
];

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

Зверніть увагу, що пісочниця вище відображає помилку у консолі:

Console
Warning: Each child in a list should have a unique “key” prop. (Попередження: Кожен дочірній елемент у списку повинен мати унікальний проп “key”.)

Далі на цій сторінці ви дізнаєтеся, як виправити цю помилку. Перш ніж перейти до цього, давайте переструктуруємо ваші дані.

Фільтрування елементів масиву

Наші дані можна структурувати більш досвідчено.

const people = [{
id: 0,
name: 'Кетрін Джонсон (Creola Katherine Johnson)',
profession: 'математик',
}, {
id: 1,
name: 'Маріо Моліна (Mario José Molina-Pasquel Henríquez)',
profession: 'хімік',
}, {
id: 2,
name: 'Абдус Салам (Moшинкаmad Abdus Salam)',
profession: 'фізик',
}, {
id: 3,
name: 'Персі Джуліан (Percy Lavon Julian)',
profession: 'хімік',
}, {
id: 4,
name: 'Субрахманьян Чандрасекар (Subrahmanyan Chandrasekhar)',
profession: 'астрофізик',
}];

Скажімо, вам потрібен спосіб показати лише людей з професією 'хімік'. Ви можете використовувати JavaScript-метод filter(), щоб отримати лише цих людей. Цей метод бере масив елементів, піддає їх “тестуванню” (функція, що повертає true або false) і повертає новий масив тільки тих елементів, які пройшли перевірку (повернули true).

Вам потрібні елементи, де професія'хімік'. Тест-функція для цього виглядає так: (person) => person.profession === 'хімік'. Ось усе разом:

  1. Створіть новий масив лише людей-хіміків, chemists, викликавши filter() для people і фільтруючи за person.profession === 'хімік':
const chemists = people.filter(person =>
person.profession === 'хімік'
);
  1. Тепер перетворіть 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. Наостанок поверніть listItems з вашого компонента:
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 === 'хімік'
  );
  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>;
}

Pitfall

Функції зі стрілками (анонімні функції JavaScript) неявно повертають вираз одразу після =>, тому вам не потрібен оператор return:

const listItems = chemists.map(person =>
<li>...</li> // Неявне повернення!
);

Однак ви повинні явно написати return, якщо за => йде фігурна дужка {!

const listItems = chemists.map(person => { // Фігурна дужка
return <li>...</li>;
});

Кажуть, що функції зі стрілками, які містять => {, мають “блок тіла”. Вони дозволяють написати більше одного рядка коду, але ви мусите вказати оператор return самостійно. Якщо ви забудете, нічого не повернеться!

Збереження порядку елементів списку за допомогою key

Зверніть увагу, що всі пісочниці вище показують помилку в консолі:

Console
Warning: Each child in a list should have a unique “key” prop. (Попередження: Кожен дочірній елемент у списку повинен мати унікальний проп “key”.)

Ви повинні надати кожному елементу масиву key — стрічкову або числову змінну, що унікально ідентифікує його серед інших елементів цього масиву:

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

Note

Елементам JSX, що є обгорткою всередині виклику map(), завжди потрібні ключі!

Ключі повідомляють React, якому елементу масиву відповідає кожен компонент, щоб він міг зіставити їх пізніше. Це важливо, якщо елементи масиву можуть змінювати позицію (наприклад, через сортування), вставлятися або видалятися. Добре підібраний key допомагає React визначити, що саме сталося, і правильно оновити дерево DOM.

Замість того, щоб генерувати ключі “на льоту”, вам слід додати їх до своїх даних:

export const people = [{
  id: 0, // Used in JSX as a key
  name: 'Кетрін Джонсон (Creola Katherine Johnson)',
  profession: 'математик',
  accomplishment: 'розрахунки для космічних польотів',
  imageId: 'MK3eW3A'
}, {
  id: 1, // Used in JSX as a key
  name: 'Маріо Моліна (Mario José Molina-Pasquel Henríquez)',
  profession: 'хімік',
  accomplishment: 'відкриття озонової діри в Арктиці',
  imageId: 'mynHUSa'
}, {
  id: 2, // Used in JSX as a key
  name: 'Абдус Салам (Moшинкаmad Abdus Salam)',
  profession: 'фізик',
  accomplishment: 'теорія електромагнетизму',
  imageId: 'bE7W1ji'
}, {
  id: 3, // Used in JSX as a key
  name: 'Персі Джуліан (Percy Lavon Julian)',
  profession: 'хімік',
  accomplishment: 'новаторські кортизоновмісні препарати, стероїди та протизаплідні таблетки',
  imageId: 'IOjWm71'
}, {
  id: 4, // Used in JSX as a key
  name: 'Субрахманьян Чандрасекар (Subrahmanyan Chandrasekhar)',
  profession: 'астрофізик',
  accomplishment: 'розрахунок мас зір категорії "білий карлик"',
  imageId: 'lrWQx8l'
}];

Deep Dive

Відображення кількох вузлів DOM для кожного елемента списку

Що робити, коли для кожного елемента потрібно відрендерити не один, а кілька вузлів DOM?

Короткий синтаксис фрагмента <>...</> не дозволить вам передати ключ, тож вам потрібно або згрупувати їх через <div>, або використати трохи довший і більш явний синтаксис <Fragment>:

import { Fragment } from 'react';

// ...

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

Фрагменти зникають із DOM, тому тут буде створено однорівневий список елементів: <h1>, <p>, <h1>, <p> і так далі.

Де взяти key

Різні джерела даних надають різні джерела ключів:

  • Дані з бази даних: Якщо ваші дані надходять із бази даних, ви можете використовувати ключі/ідентифікатори бази даних, які є унікальними за своєю природою.
  • Локально створені дані: Якщо ваші дані генеруються та зберігаються локально (наприклад, нотатки в застосунку для створення нотаток), використовуйте лічильник з інкрементом під час створення елементів — crypto.randomUUID() або пакет на зразок uuid.

Правила для ключів

  • Ключі мають бути унікальними для елементів одного рівня. Проте можна використовувати однакові ключі для вузлів JSX у різних масивах.
  • Ключі не повинні змінюватися, бо інакше це не відповідає їхньому призначенню! Не створюйте їх під час рендерингу.

Навіщо React потрібні ключі?

Уявіть, що файли на вашому робочому столі не мають імен. Замість цього ви посилаєтеся на них по порядку — перший файл, другий файл тощо. Ви можете звикнути до цього, але як тільки ви видалите файл, все сплутається. Другий файл стане першим файлом, третій файл стане другим файлом і так далі.

Імена файлів у каталозі та JSX-ключі у масиві мають схожу мету. Вони дозволяють нам унікально ідентифікувати об’єкт на одному рівні. Добре підібраний ключ надає більше інформації, ніж позиція у масиві. Навіть якщо позиція змінюється через під час зміни порядку, key дозволяє React ідентифікувати елемент протягом усього його життя.

Pitfall

У вас може виникнути спокуса використати індекс елемента в масиві як його ключ. Насправді це те, що використовуватиме React, якщо ви взагалі не вкажете key. Але порядок, у якому ви відтворюєте елементи, змінюватиметься з часом, якщо елемент буде вставлено, видалено, або якщо масив буде змінено. Індекс як ключ часто веде до непомітних і заплутаних помилок.

Так само не генеруйте ключі “на льоту”, наприклад, з key={Math.random()}. Це закінчиться тим, що ключі ніколи не збігатимуться між рендерами, і всі ваші компоненти та DOM щоразу створюватимуться заново. Це не тільки повільно, але й втратить дані, введені користувачем всередині елементів списку. Натомість використовуйте постійний ідентифікатор на основі даних.

Зверніть увагу, що ваші компоненти не отримають key як проп. Він використовується лише як підказка самим React. Якщо вашому компоненту потрібен ідентифікатор, ви повинні передати його як окремий проп: <Profile key={id} userId={id} />.

Recap

На цій сторінці ви дізналися:

  • Як перемістити дані з компонентів у структури даних як масиви та об’єкти.
  • Як створити набори подібних компонентів за допомогою map() з JavaScript.
  • Як створити масиви відфільтрованих елементів за допомогою filter() з JavaScript.
  • Навіщо і як задати key для кожного компонента в колекції, щоб React міг відстежувати кожен із них, навіть якщо їхня позиція чи дані зміняться.

Challenge 1 of 4:
Розбиття одного списку на два

Цей приклад показує список усіх людей.

Змініть його, щоб відобразити один за одним два окремі списки: Хіміки та Усі інші. Як і раніше, ви можете визначити, чи є особа хіміком, із порівняння person.profession === 'хімік'.

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>Науковці</h1>
      <ul>{listItems}</ul>
    </article>
  );
}