Перейти к содержанию

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

Сначала давайте вспомним, как работать со списками в 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'))

Посмотреть на CodePen

Этот код выведет список чисел от 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'))

Посмотреть на CodePen

Ключи

Ключи помогают 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'))

Посмотреть на CodePen

Как правило, элементам внутри 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'))

Посмотреть на CodePen

Ключи служат подсказками для 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>
  )
}

Посмотреть на CodePen

Иногда это приводит к более чистому коду, но бывает и наоборот. Как и в любом JavaScript-коде, вам придётся самостоятельно решать, стоит ли извлекать код в переменную ради читабельности. Не забывайте, что если код внутри map() слишком громоздкий, имеет смысл извлечь его в отдельный компонент.