Почти любой интерфейс — это списки: товары в каталоге, сообщения в чате,
строки таблицы. Данные приходят массивом, а показать нужно по элементу на
каждую запись. Цикла for в JSX нет, но он и не нужен — есть map.
map: из данных — элементы
map превращает массив данных в массив JSX-элементов. А массив элементов
React умеет рисовать подряд:
function Fruits() {
const fruits = ['яблоко', 'груша', 'слива']
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
)
}
// <ul><li>яблоко</li><li>груша</li><li>слива</li></ul>Это то же, что написать вручную:
<ul>
{[<li key="яблоко">яблоко</li>, <li key="груша">груша</li>, <li key="слива">слива</li>]}
</ul>Обратите внимание на круглые скобки после стрелки: (fruit) => (<li>…</li>)
сразу возвращает JSX. С фигурными { } понадобится return, без него
функция вернёт undefined, и список окажется пустым.
key: имя для каждого элемента
Атрибут key — подпись, по которой React отличает элементы списка друг от
друга. Без него в выводе появится предупреждение:
React: Each child in a list should have a unique "key" prop.Зачем это нужно? Список меняется: задачу удалили, товар добавили, порядок
отсортировали. React сравнивает новый список со старым и по ключам понимает,
какой элемент куда делся: «key=7 переехал наверх, key=3 удалили». Без
ключей ему остаётся сравнивать по порядку — и перерисовывать больше, чем нужно,
а иногда и путать, к какой строке относится введённый текст или галочка.
Правила для ключа:
- уникален среди соседей — в пределах одного списка;
- стабилен — у одной и той же записи всегда один ключ;
- лучше всего —
idиз данных:<li key={user.id}>.
Если записи — уникальные строки, ключом может быть сама строка, как
fruit выше.
Индекс как ключ — только для неизменных списков
У map есть второй аргумент — номер элемента. Его соблазнительно взять в
ключ:
{todos.map((todo, index) => <li key={index}>{todo.text}</li>)}Предупреждения не будет, но смысла в таком ключе мало. Удалите первую задачу
— и все остальные «сдвинутся»: у бывшей второй теперь ключ 0. React решит,
что изменился текст у каждой строки, а пропала последняя. Индекс годится,
только если список никогда не меняется: не сортируется, не фильтруется, в
середину ничего не вставляют. Есть id — берите id.
key нужен React, а не вашему компоненту: прочитать его через props нельзя.
Если id нужен и внутри — передайте его отдельным prop.
Ключ — на внешнем элементе
Ключ ставят на то, что возвращает map, а не глубже:
function UserRow({ user }) {
return <li>{user.name}</li>
}
function UserList({ users }) {
return (
<ul>
{users.map((user) => (
<UserRow key={user.id} user={user} />
))}
</ul>
)
}Здесь map возвращает <UserRow> — на нём и key. Внутри UserRow
писать <li key=…> бесполезно.
Сначала отобрать, потом нарисовать
map рисует всё, что получил. Если показать нужно не всё или в другом
порядке — подготовьте массив заранее, обычными методами массивов:
function CheapProducts({ products }) {
const cheap = products
.filter((p) => p.price < 1000)
.sort((a, b) => a.price - b.price)
return (
<ul>
{cheap.map((p) => (
<li key={p.id}>{p.title} — {p.price} ₽</li>
))}
</ul>
)
}Осторожно с sort: он меняет исходный массив, а массив пришёл в props —
менять его нельзя. Здесь всё в порядке, потому что filter уже вернул новый
массив. Если сортируете без фильтра, сначала сделайте копию:
[...products].sort(...).
Пустой список
Когда массив пуст, map вернёт [], и на экране останется голый <ul>.
Пользователю лучше сказать прямо, что ничего нет. Тут пригодится условный
рендер из прошлого урока:
function Comments({ comments }) {
if (comments.length === 0) {
return <p>Комментариев пока нет</p>
}
return (
<ul>
{comments.map((c) => (
<li key={c.id}>{c.text}</li>
))}
</ul>
)
}И снова про ловушку: {comments.length && <ul>…</ul>} при пустом массиве
нарисует «0». Пишите comments.length > 0 &&.
Вложенные списки
Если у записей есть свои массивы, map вкладывается в map. Чтобы не
запутаться в скобках, внутренний список удобно вынести в отдельный
компонент:
function Group({ group }) {
return (
<li>
{group.name}
<ul>
{group.members.map((m) => (
<li key={m.id}>{m.name}</li>
))}
</ul>
</li>
)
}
function Groups({ groups }) {
return (
<ul>
{groups.map((g) => (
<Group key={g.id} group={g} />
))}
</ul>
)
}Ключи нужны в каждом списке. Уникальными они должны быть только среди
соседей: у двух разных групп могут быть участники с одинаковыми id.
Первый модуль пройден
Это был последний бесплатный урок. Посмотрите, что вы уже умеете: писать компоненты на JSX, передавать им данные через props, показывать разное по условию и выводить списки из массива с правильными ключами. Этого достаточно, чтобы собрать статичную страницу из компонентов.
Дальше страница оживает:
- Состояние и события. Обработчики кликов,
useState, формы и массивы в состоянии. - Компоненты вместе. Поднятие состояния,
useReducerи контекст. - Эффекты и данные.
useEffect, загрузка данных с сервера, свои хуки иuseRef. - Проект. Интернет-магазин с фильтрами, корзиной и загрузкой данных.
Остальные модули открываются после покупки курса: один платёж, доступ навсегда. Купить можно на странице курса.