HOW TO React

Рендеринг списков

Часто возникает необходимость отобразить несколько похожих компонентов из набора данных. Для работы с массивом данных можно использовать методы массивов JavaScript. На этой странице вы будете использовать filter() и map() с React для фильтрации и преобразования массива данных в массив компонентов.

Вы узнаете

  • Как отображать компоненты из массива с помощью map() в JavaScript
  • Как отображать только определенные компоненты с помощью filter() в JavaScript
  • Когда и зачем использовать ключи React

Отображение данных из массивов

Предположим, у вас есть список контента.

<ul>
  <li>Креола Кэтрин Джонсон: математик</li>
  <li>Марио Хосе Молина-Паскель Энрикес: химик</li>
  <li>Мохаммад Абдус Салам: физик</li>
  <li>Перси Лавон Джулиан: химик</li>
  <li>Субраманьян Чандрасекар: астрофизик</li>
</ul>

Единственное различие между этими элементами списка — их содержимое, их данные. При создании интерфейсов вам часто приходится отображать несколько экземпляров одного и того же компонента с использованием разных данных: от списков комментариев до галерей аватаров. В таких ситуациях вы можете хранить эти данные в объектах и массивах JavaScript и использовать такие методы, как map() и filter() для визуализации списков компонентов на их основе.

Вот небольшой пример того, как сгенерировать список элементов из массива:

  1. Перенесите данные в массив:
const people = [
  'Creola Katherine Johnson: mathematician',
  'Mario José Molina-Pasquel Henríquez: chemist',
  'Mohammad Abdus Salam: physicist',
  'Percy Lavon Julian: chemist',
  'Subrahmanyan Chandrasekhar: astrophysicist'
];
  1. Преобразуйте элементы people в новый массив узлов JSX, listItems:
const listItems = people.map(person => <li>{person}</li>);
  1. Верните listItems из вашего компонента, обернув его в <ul>:
return <ul>{listItems}</ul>;

Вот результат:

const people = [
  'Креола Кэтрин Джонсон: математик',
  'Марио Хосе Молина-Паскель Энрикес: химик',
  'Мохаммад Абдус Салам: физик',
  'Перси Лавон Джулиан: химик',
  'Субраманьян Чандрасекар: астрофизик'
];

export default function List() {
  const listItems = people.map(person =>
    <li>{person}</li>
  );
  return <ul>{listItems}</ul>;
}

Обратите внимание, что отображается ошибка консоли:

Warning: Each child in a list should have a unique “key” prop.

Вы узнаете, как исправить эту ошибку, далее на этой странице. Прежде чем мы к этому перейдем, давайте добавим некоторую структуру в ваши данные.

Фильтрация массивов элементов

Эти данные можно структурировать еще лучше.

const people = [{
  id: 0,
  name: 'Creola Katherine Johnson',
  profession: 'mathematician',
}, {
  id: 1,
  name: 'Mario José Molina-Pasquel Henríquez',
  profession: 'chemist',
}, {
  id: 2,
  name: 'Mohammad Abdus Salam',
  profession: 'physicist',
}, {
  
id: 3,
  name: 'Percy Lavon Julian',
  profession: 'chemist',
}, {
  id: 4,
  name: 'Subrahmanyan Chandrasekhar',
  profession: 'astrophysicist',
}];

Допустим, вам нужно отобразить только тех людей, чья профессия — 'химик'. Вы можете использовать метод filter() в JavaScript, чтобы вернуть только этих людей. Этот метод принимает массив элементов, пропускает их через 'тест' (функцию, возвращающую true или false) и возвращает новый массив, содержащий только те элементы, которые прошли тест (вернули true).

Вам нужны только элементы, у которых profession равно 'chemist'. Функция-'тест' для этого выглядит как (person) => person.profession === 'chemist'. Вот как это собрать воедино:

  1. Создайте новый массив, содержащий только 'химиков', chemists, вызвав filter() на people с фильтрацией по person.profession === 'chemist':
const chemists = people.filter(person =>
  person.profession === 'chemist'
);
  1. Теперь проведите map над chemists:
const listItems = chemists.map(person =>
  <li>
    <img
       src={getImageUrl(person)}
       alt={person.name}
     />
     <p>
       <b>{person.name}:</b>
       {' ' + person.profession + ' '}
       известен {person.accomplishment}
     </p>
  </li>
);
  1. Наконец, возвращаем listItems из вашего компонента:
return <ul>{listItems}</ul>;
//App.js
import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const chemists = people.filter(person =>
    person.profession === 'chemist'
  );
  const listItems = chemists.map(person =>
    <li>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        known for {person.accomplishment}
      </p>
    </li>
  );
  return <ul>{listItems}</ul>;
}

Стрелочные функции неявно возвращают выражение, следующее сразу за =>, поэтому вам не нужно было использовать оператор return:

const listItems = chemists.map(person =>
  <li>...</li> // Неявное возвращение!
);

Однако вы должны явно написать return, если за вашим => следует фигурная скобка {!

const listItems = chemists.map(person => { // Фигурная скобка
  return <li>...</li>;
});

Считается, что стрелочные функции, содержащие => {, имеют 'тело блока'. Они позволяют вам писать более одной строки кода, но вы обязаны самостоятельно написать оператор return. Если вы забудете это сделать, ничего не будет возвращено!

Сохранение порядка элементов списка с помощью key

Обратите внимание, что все приведенные выше примеры в консоли показывают ошибку:

Warning: Each child in a list should have a unique “key” prop.

Вам нужно присвоить каждому элементу массива key — строку или число, которое однозначно идентифицирует его среди других элементов в этом массиве:

<li key={person.id}>...</li>

Элементы JSX, расположенные непосредственно внутри вызова map(), всегда нуждаются в ключах!

Ключи сообщают React, какому элементу массива соответствует каждый компонент, чтобы он мог сопоставить их позже. Это становится важным, если элементы вашего массива могут перемещаться (например, из-за сортировки), вставляться или удаляться. Правильно выбранный key помогает React понять, что именно произошло, и внести правильные изменения в дерево DOM.

Вместо того чтобы генерировать ключи на лету, вам следует включить их в ваши данные:

import { people } from './data.js';import { getImageUrl } from './utils.js';export default function List() {  const listItems = people.map(person =>    <li key={person.id}>      <img        src={getImageUrl(person)}        alt={person.name}      />      <p>        <b>{person.name}</b>          {' ' + person.profession + ' '}          known for {person.accomplishment}      </p>    </li>  );  return <ul>{listItems}</ul>;}
Preview

Отображение нескольких узлов DOM для каждого элемента списка

Что делать, если для каждого элемента необходимо отобразить не один, а несколько узлов DOM?

Краткий синтаксис [<>... </> не позволяет передать ключ, поэтому вам нужно либо сгруппировать их в один <div>, либо использовать немного более длинный и более явный синтаксис <Fragment>:

import { Fragment } from 'react';

// ...

const listItems = people.map(person =>
  <Fragment key={person. id}>
    <h1>{person.name}</h1>
    <p>{person.bio}</p>
  </Fragment>
);

Фрагменты исчезают из DOM, поэтому в результате получится плоский список из <h1>, <p>, <h1>, <p> и так далее.

Где взять свой key

Различные источники данных предоставляют различные источники ключей:

  • Данные из базы данных: Если ваши данные поступают из базы данных, вы можете использовать ключи/ID базы данных, которые по своей природе являются уникальными.

Локально сгенерированные данные: Если ваши данные генерируются и сохраняются локально (например, заметки в приложении для ведения заметок), используйте инкрементируемый счетчик, crypto.randomUUID() или пакет типа uuid при создании элементов.

Правила ключей

  • Ключи должны быть уникальными среди элементов одного уровня. Однако можно использовать одинаковые ключи для узлов JSX в разных массивах.
  • Ключи не должны меняться, иначе это противоречит их назначению! Не генерируйте их во время рендеринга.

Зачем React нужны ключи?

Представьте, что файлы на вашем рабочем столе не имели бы имен. Вместо этого вы бы обращались к ним по порядку — первый файл, второй файл и так далее. Вы могли бы к этому привыкнуть, но как только вы удалите файл, все станет запутанным. Второй файл станет первым, третий — вторым и так далее.

Имена файлов в папке и ключи JSX в массиве служат одной и той же цели. Они позволяют нам однозначно идентифицировать элемент среди его соседей. Хорошо выбранный ключ предоставляет больше информации, чем позиция в массиве. Даже если позиция изменится из-за переупорядочения, key позволяет React идентифицировать элемент на протяжении всего его жизненного цикла.

У вас может возникнуть соблазн использовать индекс элемента в массиве в качестве его ключа. Фактически, именно это и будет использовать React, если вы вообще не укажете key. Но порядок, в котором вы отображаете элементы, будет меняться со временем, если элемент будет вставлен, удален или если массив будет переупорядочен. Использование индекса в качестве ключа часто приводит к незаметным и запутанным ошибкам.

Точно так же не генерируйте ключи на лету, например, с помощью key={Math.random()}. Это приведет к тому, что ключи никогда не будут совпадать между рендерингами, в результате чего все ваши компоненты и DOM будут пересоздаваться каждый раз. Это не только замедляет работу, но и приводит к потере любого пользовательского ввода внутри элементов списка. Вместо этого используйте стабильный ID, основанный на данных.

Обратите внимание, что ваши компоненты не будут получать key в качестве пропса. Он используется только в качестве подсказки самим React. Если вашему компоненту нужен ID, вам нужно передать его как отдельный проп: <Profile key={id} userId={id} />.

Вывод

На этой странице вы узнали:

  • Как выносить данные из компонентов в структуры данных, такие как массивы и объекты.
  • Как генерировать наборы похожих компонентов с помощью map() в JavaScript.
  • Как создавать массивы отфильтрованных элементов с помощью filter() в JavaScript.
  • Почему и как устанавливать key для каждого компонента в коллекции, чтобы React мог отслеживать каждый из них, даже если их позиция или данные изменятся.

On this page