2.9 Списки и ключи

Сперва давайте посмотрим как можно преобразовывать списки в JavaScript.

В данном коде ниже мы используем функцию map(), чтобы принять массив numbers и помножить на 10 его значения. Мы присваиваем новый массив, возвращенный методом map(), переменной result и логируем ее:


Код
        
  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 преобразование массивов в списки элементов почти идентично.


2.9.1 Отрисовка группы компонентов

Вы можете создавать коллекции элементов и включать их в JSX. Используя фигурные скобки.

Ниже, мы проходимся в цикле по массиву users, используя функцию map() JavaScript. Мы возвращаем элемент <li> для каждого элемента массива. В итоге мы присваиваем результирующий массив элементов переменной items:


Код
        
  const users = ['Вася', 'Петя', 'Максим', 'Егор'];
  const items = users.map((user) => <li>{user}</li>);
  console.log(items);
    

Мы включаем весь массив items внутри элемента <ul> и отрисовываем его в DOM:


Код
        
  ReactDOM.render(<ul>{items}</ul>, document.getElementById('root'));
    

Посмотреть в CodePen


Этот код отображает маркированный список всех пользователей.


2.9.2 Базовый компонент списка

В большинстве случаев вы бы отрисовывали списки внутри компонента.

Мы можем отрефакторить предыдущий пример и выделить компонент, который принимает массив 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'));
    

Посмотреть в CodePen



2.9.3 Ключи

Ключи помогают 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 из ваших данных как ключи:


Код
        
  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>
  );
    

Мы не рекомендуем использовать индексы для ключей, если элементы могут переупорядочиваться, так как это будет выполняться медленно. Вы можете прочитать более глубокое объяснение о том, почему ключи необходимы, если вам интересно.


2.9.4 Выделение компонентов с ключами

Ключи имеют смысл только в контексте окружающего массива.

К примеру, если вы выделяете компонент 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'));
    

Посмотреть в CodePen


Хорошим эмпирическим правилом является то, что элементы внутри вызова map() требуют ключей.


2.9.5 Ключи должны быть уникальными только в пределах соседей

Ключи использующиеся вместе с массивами должны быть уникальными только в пределах их соседей. Тем не менее, они не требуют быть уникальными глобально. Вы можете использовать те же самые ключи, когда производите два различных массива:


Код
        
  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.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'));
    

Посмотреть в CodePen


Ключи служат как подсказки React, но они не передаются в ваши компоненты. Если вам нужно то же самое значение в вашем компоненте, передайте его напрямую как свойство с другим именем:


Код
        
  const content = messages.map((message) =>
    <Message key={message.id} id={message.id} text={message.text}/>
  );
    

В примере выше, компонент Message может читать props.id, но не props.key.


2.9.6 Встраивание map() в JSX

В примерах выше мы объявляли отдельную переменную 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() имеет слишком много вложений, возможно, наступило отличное время выделить компонент.