Списки и ключи
Сначала давайте вспомним, как работать со списками в JavaScript.
В коде ниже мы используем функцию map()
, чтобы удвоить значения в массиве numbers
. Мы присваиваем массив, возвращаемый из map()
, переменной doubled
, и выводим её в консоль:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);console.log(doubled);
Этот код выведет [2, 4, 6, 8, 10]
в консоль.
В React преобразование массивов в список элементов выглядит похожим образом.
Рендер нескольких компонентов
Вы можете создать коллекцию элементов и встроить её в JSX с помощью фигурных скобок {}
.
К примеру, пройдём по массиву numbers
, используя функцию JavaScript map()
, и вернём элемент <li>
в каждой итерации. Получившийся массив элементов сохраним в listItems
:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => <li>{number}</li>);
Теперь мы включим массив listItems
внутрь элемента <ul>
и отрендерим его в DOM:
ReactDOM.render(
<ul>{listItems}</ul>, document.getElementById('root')
);
Этот код выведет список чисел от 1 до 5.
Простой компонент-список
Как правило, вы будете рендерить списки внутри какого-нибудь компонента.
Мы можем отрефакторить предыдущий пример с использованием компонента, который принимает массив numbers
и выводит список элементов.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) => <li>{number}</li> ); return (
<ul>{listItems}</ul> );
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />, document.getElementById('root')
);
Когда вы запустите данный код, то увидите предупреждение о том, что у каждого элемента массива должен быть ключ (key). «Ключ» — это специальный строковый атрибут, который нужно указывать при создании списка элементов. Мы обсудим, почему это важно, ниже на странице.
Чтобы исправить проблему с неуказанными ключами, добавим каждому элементу в списке атрибут key
.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}> {number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Ключи
Ключи помогают React определять, какие элементы были изменены, добавлены или удалены. Их необходимо указывать, чтобы React мог сопоставлять элементы массива с течением времени:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}> {number}
</li>
);
Лучший способ выбрать ключ — это использовать строку, которая будет явно отличать элемент списка от его соседей. Чаще всего вы будете использовать ID из ваших данных как ключи:
const todoItems = todos.map((todo) =>
<li key={todo.id}> {todo.text}
</li>
);
Когда у вас нет заданных ID для списка, то в крайнем случае можно использовать индекс элемента как ключ:
const todoItems = todos.map((todo, index) =>
// Делайте так, только если у элементов массива нет заданного ID <li key={index}> {todo.text}
</li>
);
Мы не рекомендуем использовать индексы как ключи, если порядок элементов может поменяться. Это негативно скажется на производительности и может вызвать проблемы с состоянием компонента. Почитайте статью Робина Покорни (Robin Pokorny), которая объясняет, почему индексы-ключи приводят к проблемам. Если вы опустите ключ для элемента в списке, то React по умолчанию будет использовать индексы как ключи.
Вот подробное объяснение о том, почему ключи необходимы.
Извлечение компонентов с ключами
Ключи нужно определять непосредственно внутри массивов.
Например, если вы извлекаете компонент ListItem
, то нужно указывать ключ для <ListItem />
в массиве, а не в элементе <li>
внутри самого ListItem
.
Пример неправильного использования ключей
function ListItem(props) {
const value = props.value;
return (
// Неправильно! Нет необходимости задавать здесь ключ: <li key={value.toString()}> {value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Неправильно! Ключ необходимо определить здесь: <ListItem value={number} /> );
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Пример правильного использования ключей
function ListItem(props) {
// Правильно! Не нужно определять здесь ключ: return <li>{props.value}</li>;}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Правильно! Ключ нужно определять внутри массива: <ListItem key={number.toString()} value={number} /> );
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Как правило, элементам внутри map()
нужны ключи.
Ключи должны быть уникальными среди соседних элементов
Ключи внутри массива должны быть уникальными только среди своих соседних элементов. Им не нужно быть уникальными глобально. Можно использовать один и тот же ключ в двух разных массивах.
function Blog(props) {
const sidebar = ( <ul>
{props.posts.map((post) =>
<li key={post.id}> {post.title}
</li>
)}
</ul>
);
const content = props.posts.map((post) => <div key={post.id}> <h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar} <hr />
{content} </div>
);
}
const posts = [
{id: 1, title: 'Привет, мир', content: 'Добро пожаловать в документацию React!'},
{id: 2, title: 'Установка', content: 'React можно установить из npm.'}
];
ReactDOM.render(
<Blog posts={posts} />,
document.getElementById('root')
);
Ключи служат подсказками для React, но они никогда не передаются в ваши компоненты. Если в компоненте нужно то же самое значение, то передайте его явно через проп с другим именем:
const content = posts.map((post) =>
<Post
key={post.id} id={post.id} title={post.title} />
);
В примере выше компонент Post
может прочитать значение props.id
, но не props.key
.
Встраивание map() в JSX
В примерах выше мы отдельно определяли переменную listItems
и вставляли её в JSX:
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) => <ListItem key={number.toString()} value={number} /> ); return (
<ul>
{listItems}
</ul>
);
}
JSX позволяет встроить любое выражение в фигурные скобки, так что мы можем включить результат выполнения map()
:
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) => <ListItem key={number.toString()} value={number} /> )} </ul>
);
}
Иногда это приводит к более чистому коду, но бывает и наоборот. Как и в любом JavaScript-коде, вам придётся самостоятельно решать, стоит ли извлекать код в переменную ради читабельности. Не забывайте, что если код внутри map()
слишком громоздкий, имеет смысл извлечь его в отдельный компонент.