2.9 Списки и ключи
Прежде всего давайте посмотрим как можно преобразовывать списки в JavaScript.
В примере ниже мы используем функцию map()
, чтобы принять массив
numbers
и помножить все его значения на 10. Мы присваиваем переменной
result
новый массив, который возвращает метод map()
, и выводим её в лог:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const result = numbers.map((num) => num * 10);
console.log(result);
Этот код логирует [10, 20, 30, 40, 50, 60, 70, 80, 90] в консоль
В React преобразование массивов в списки элементов почти идентично.
В React вы можете создавать коллекции элементов и включать их в JSX. Для
этого нужно использовать фигурные скобки {}
.
В примере кода ниже, мы в цикле проходимся по массиву users
, используя JavaScript-функцию
map()
. Мы возвращаем элемент <li>
для каждого элемента
массива. В итоге мы присваиваем результирующий массив элементов переменной items
:
const users = ['Вася', 'Петя', 'Максим', 'Егор'];
const items = users.map((user) => <li>{user}</li>);
console.log(items);
Далее мы заключаем весь массив React-элементов items
внутри элемента
<ul>
и отрисовываем его в DOM:
ReactDOM.render(<ul>{items}</ul>, document.getElementById('root'));
Этот код отображает маркированный список всех пользователей.
Обычно в приложениях мы отрисовываем списки внутри какого-нибудь компонента.
Давайте отрефакторим предыдущий пример, выделив компонент, который
принимает массив users
и выводит несортированный список элементов:
const users = ['Вася', 'Петя', 'Максим', 'Егор'];
function UserList(props){
const users = props.users;
const items = users.map((user) => <li>{user}</li>);
return (<ul>{items}</ul>)
}
ReactDOM.render(<UserList users={users}/>, document.getElementById('root'));
Когда вы выполните этот код, то получите предупреждение, что для списка элементов должен быть
предоставлен ключ key
. «Keys» - это специальные строковые атрибуты, которые
вам нужно добавить, создавая список элементов. Почему это так важно, мы обсудим в следующем разделе.
Давайте присвоим ключ key
к элементам нашего списка внутри numbers.map()
и
исправим проблему пропущенных ключей:
const users = ['Вася', 'Петя', 'Максим', 'Егор'];
function UserList(props){
function getKey(str){
let key = 0;
for (let i = 0; i < str.length; i++) {
key += str.charCodeAt(i);
}
return key.toString();
}
const users = props.users;
const items = users.map((user) => {
const key = getKey(user)
return <li key={key}>{user}</li>;
});
return (<ul>{items}</ul>);
}
ReactDOM.render(<UserList users={users}/>, document.getElementById('root'));
Ключи помогают React идентифицировать, какой элемент был изменен, добавлен или удален. Ключи должны быть предоставлены элементам внутри массива, чтобы дать им стабильную идентифицируемость:
function getKey(str){
let key = 0;
for (let i = 0; i < str.length; i++) {
key += str.charCodeAt(i);
}
return key.toString();
}
const users = ['Вася', 'Петя', 'Максим', 'Егор'];
const items = users.map((user) => {
const key = getKey(user)
return <li key={key}>{user}</li>;
});
Лучший способ выбрать ключи – использовать строку, которая уникально идентифицирует элемент списка среди его соседей. В связи с этим, в качестве ключа вам следует использовать ID из ваших данных, принятых с сервера:
// Данные с сервера могут иметь примерно следующий вид: [{id: 1, name: 'Вася'}, ...]
const userList = users.map((user) =>
<li key={user.id}>
{user.name}
</li>
);
В крайнем случае, когда у вас нет стабильных ID для отрисовки элементов, вы можете использовать как ключ индекс элемента:
const userList = users.map((user, index) =>
// Делать так можно только в том случае, если у данных нет стабильных ID
<li key={index}>
{user.name}
</li>
);
Мы не рекомендуем использовать индексы в качестве ключей, если элементы могут быть переупорядочены, так как такая операция будет выполняться медленно. Если вам интересно, то можете прочитать более глубокое объяснение о том, почему ключи необходимы.
Ключи имеют смысл только в контексте окружающего массива.
К примеру, если вы выделяете компонент UserItem
, вам следует поставить
ключи на все элементы <UserItem />
, а не на дочерние
элементы <li>
.
Пример неправильного использования ключей:
const users = [{id: 1, name: 'Вася'},
{id: 2, name: 'Петя'},
{id: 3, name: 'Максим'},
{id: 4, name: 'Егор'}];
function UserItem(props){
const user = props.user
//Неправильно! Здесь не нужно указывать ключ:
return (<li key={user.id}>{user.name}</li>)
}
function UserList(props){
const users = props.users;
const items = users.map((user) => {
//Неправильно! Здесь должен быть указан ключ:
return <UserItem user={user}/>;
});
return (<ul>{items}</ul>);
}
ReactDOM.render(<UserList users={users}/>, document.getElementById('root'));
Пример правильного использования ключей:
const users = [{id: 1, name: 'Вася'},
{id: 2, name: 'Петя'},
{id: 3, name: 'Максим'},
{id: 4, name: 'Егор'}];
function UserItem(props){
const user = props.user
//Правильно! Здесь не нужно указывать ключ:
return (<li>{user.name}</li>)
}
function UserList(props){
const users = props.users;
const items = users.map((user) => {
//Правильно! Здесь должен быть указан ключ:
return <UserItem key={user.id} user={user}/>;
});
return (<ul>{items}</ul>);
}
ReactDOM.render(<UserList users={users}/>, document.getElementById('root'));
Вот хорошее эмпирическое правило: элементы внутри
вызова map()
требуют ключей.
Ключи, связанные с определенным массивом, должны быть уникальными только в пределах своих соседей. Однако, они не должны быть уникальными глобально. Вы можете использовать те же самые ключи для двух разных массивов:
function Chat(props) {
const users = props.users;
const userList = (
<p> Пользователи чата:
{users.map((user) =>
<b key={user.id}> {user.name}; </b>
)}
</p>
);
const messageList = props.messages.map((message) => {
let author = null;
// Находим автора сообщения в списке users
users.forEach((user) => {if(user.id === message.authorId) author = user});
return (
<p key={message.id}>
<b>{author.name}: </b>
<span>{message.message}</span>
</p>
)
});
return (
<p>
{userList}
{messageList}
</p>
);
}
const users = [
{id: 1, name: 'Вася'},
{id: 2, name: 'Петя'},
{id: 3, name: 'Ваня'}
];
const messages = [
{id: 1, message: 'Всем привет!', authorId: 1},
{id: 2, message: 'И тебе привет!', authorId: 2},
{id: 3, message: 'Привет, Вася :)', authorId: 3}
];
ReactDOM.render(<Chat users={users} messages={messages}/>, document.getElementById('root'));
Ключи служат подсказками библиотеке React для корректной отрисовки списка элементов. Однако они не передаются в ваши компоненты. Если вам нужно такое же значение и в вашем компоненте, передайте его напрямую как свойство с другим именем:
const content = messages.map((message) =>
<Message key={message.id} id={message.id} text={message.text}/>
);
В примере выше, компонент Message
видит только props.id
,
но не props.key
.
В примерах выше мы объявляли отдельную переменную items
и включали ее в JSX:
function UserList(props){
const users = props.users;
const items = users.map((user) => <UserItem user={user}/>);
return (<ul>{items}</ul>);
}
JSX позволяет встраивать в фигурные скобки любое выражение.
Поэтому мы можем встроить результат вызова map()
:
function UserList(props){
const users = props.users;
return (
<ul>
{users.map((user) => <UserItem user={user}/>;)}
</ul>
);
}
Иногда такой приём приводит к более чистому коду, но злоупотреблять этим стилем не следует.
Как и в JavaScript, вам решать, что лучше в плане читабельности: вынесение кода в отдельную
переменную, либо вызов функции, как мы показали выше. Имейте в виду, что если тело map()
имеет слишком много вложений, возможно, наступило отличное время выделить компонент.