Паттерн "Список и Детали"
Автор конспекта: Бадалова Елена
Практически все, с чем мы взаимодействуем в вебе, — это списки: список друзей, транзакций, фильмов,
музыки, диалогов. Паттерн "Список и Детали" (List-Detail) — один из фундаментальных в
UI-дизайне. Но зачем он нужен? Все просто: мы видим большой список, чтобы из него что-то выбрать и
посмотреть детали. Сначала мы получаем общее представление с минимумом информации, а затем
фокусируемся на чем-то одном.
Цель этого урока — научиться мастерски программировать этот паттерн в React. Мы разберем ключевую
связь между данными (состоянием) и пользовательским интерфейсом, пройдя путь от простого решения к
профессиональному, которое обеспечивает идеальный пользовательский опыт.
Способы отображения деталей могут варьироваться: рядом со списком (двухколоночный макет), на отдельной странице (через переход по ссылке), под выбранным элементом (аккордеон), во всплывающем окне (модальное окно или pop-up).

State и UI
Ключ к созданию эффективного интерфейса — это четкая связь между состоянием приложения (State) и
тем, что пользователь видит на экране (UI). Правильная архитектура данных позволяет React
реактивно обновлять интерфейс в ответ на изменения, создавая предсказуемое поведение.
Мир данных (State / Data Structures) — это структуры данных (переменные, массивы, объекты),
которые полностью описывают текущее состояние приложения.
Мир отображения (Render Algorithm) — это то, что непосредственно занимается отрисовкой
элементов на экране, формируя пользовательский интерфейс (UI).
Любое изменение, которое должно быть отражено на экране, должно сначала произойти в стейте.
Исходные данные
tracks(массив объектов) - Хранит список всех треков для отображения.selectedTrackId(идентификатор выбранного трека, который может быть null или фактическимID) - ХранитIDвыбранного трека. Еслиnull, ни один трек не выбран.

Задача
При клике на трек в списке List, необходимо выделить его (например, рамкой) и показать информацию
о нем в блоке Detail.

Подготовим вёрстку
1. Получение деталей в локальном массиве
🔶 Сохранение выбранного трека во внешней переменной внутри цикла map во время рендеринга списка
Категорически не рекомендуется
⚠️ Недостатки:
-
Код становится хрупким, модуль отрисовки списка начинает зависеть от побочного эффекта (изменения внешней переменной), что нарушает принцип единой ответственности.
-
Callbackфункция, передаваемая в.map(), не должна иметь побочных эффектов (т.е. изменять внешние переменные). Ее задача — только преобразование данных.
🔶 Вычисление данных до возврата JSX
Второй подход — это вычислять данные о выбранном треке прямо в момент рендеринга. Нам ведь нужен не
просто ID, а весь объект трека. Мы можем легко найти его в массиве tracks с помощью метода
find перед возвращением JSX.
Стрелочная функция, которую мы передаём в метод find(), называется функция-предикат.
Функция-предикат – это функция, которая всегда возвращает логическое значение (true или
false), проверяя некоторое условие или утверждение о переданных данных.
selectedTrack является производным от уже существующих данных (tracks и selectedId), и для
его хранения не обязательно заводить дополнительный стейт.
✅ Ключевые преимущества этого подхода:
-
Отсутствует дублирование данных. Вся информация о треках хранится только в одном месте — в массиве
tracks. -
Производные данные не могут "рассинхронизироваться". Переменная
selectedTrackвсегда будет актуальна, так как вычисляется заново при каждом рендере на основе текущего состояния. -
Этот подход является нормальным. Однако он становится неприменимым в асинхронных сценариях.
🔶 Асинхронные данные и проблема UX
Сейчас мы рассмотрим версию, где при клике на элемент, мы будем сохранять не id выбранного трека,
а сам объект выбранного трека (selectedTrack) в отдельной переменной состояния (useState). Т.е.
мы будем хранить отдельно идентификатор и отдельно трек.

Создание взаимосвязанных состояний (selectedTrackId и selectedTrack) увеличивает риск
рассогласованности данных, если они меняются не синхронно.
Вызовем set функцию для изменения состояния (setSelectedTrack) напрямую внутри тела компонента
App (в процессе рендеринга).
Крайне некорректная реализация
Компонент должен только возвращать JSX. Изменение состояния напрямую во время рендеринга является
побочным эффектом, который может привести к непредсказуемому поведению и бесконечным циклам
перерисовки. Это аналогично выполнению запроса на сервер непосредственно в теле компонента.
Корректные места для асинхронных операций (изменения состояния, запросы на сервер)
-
Обработчики событий (
Event Handlers) - Основное место для изменения состояния и выполнения запросов на сервер, которые инициируются действиями пользователя (например, кликом). -
Хук
useEffect- Для более сложных операций, запросов, выполняемых при монтировании/обновлении компонента, или при изменениях других состояний/пропсов.
Переносим логику в обработчики событий (onClick)
✅ Ключевые преимущества этого подхода:
- Нет необходимости в
findвнутриmap, так как объект трека уже доступен в замыкании обработчика клика. - Если несколько
setфункций вызываются внутри одного обработчика события,Reactобъединяет их, вызывая компонентAppтолько один раз для перерисовки после всех изменений.
2. Получение деталей с сервера
В реальных приложениях сервер редко отдает все данные сразу. Детальная информация, как тексты песен
(lyrics) или история сообщений в мессенджере, может быть очень объемной. Какова вероятность, что
пользователь захочет увидеть тексты всех 1000 песен из списка? Загружать все это заранее — крайне
неэффективно.
Для получения полной детализации требуется отдельный запрос на сервер по идентификатору выбранного элемента.
-
Первый запрос (при загрузке) - Получаем основной список треков (
ID, название), но без текстов. -
Второй запрос (по клику) - Когда пользователь выбирает трек, мы делаем отдельный асинхронный запрос, чтобы получить детальную информацию о конкретном треке, включая текст песни.
Идем в документацию API и находим данный
endpoint,
который возвращает детали трека по его ID.

Запрос на получение деталей конкретного трека целесообразно делать в обработчике события onClick
на элементе списка. Это логично, так как действие инициируется пользователем.
Итоговый результат: мы кликаем по треку и в списке деталей видим текст песни (lyrics) 🚀
Проблема с отзывчивостью (UX)
Если избавиться от selectedTrackId, то при медленном интернете возникает серьёзная проблема с
пользовательским опытом.
Когда пользователь кликает на трек:
- Отправляется запрос на сервер.
- Интерфейс "замирает". Ничего не происходит, пока не придёт ответ.
- Только после получения ответа трек подсвечивается и одновременно появляются его детали.
Пользователь не понимает, что его действие (клик) было принято системой, и не видит немедленной реакции интерфейса, он может начать кликать повторно. Это приводит к отправке множества лишних запросов на сервер.
Чтобы решить проблему UX, мы сознательно идём на архитектурное решение: разделить состояние и
инициировать две последовательные перерисовки (рендера).
-
selectedTrackId- для мгновенного откликаUI(подсветка элемента). -
selectedTrackдля отложенного отображения полных данных, полученных с сервера.
Такое разделение обеспечивает высокую отзывчивость UI, не заставляя пользователя ждать загрузки
всех деталей для получения визуального отклика на его действие.
🏠 Домашнее задание
Цель задания:
Изучить и реализовать UI/UX паттерн "Список-Детали" (List-Detail) на практике, используя API для
получения списка задач и их детальной информации.
Продолжай работать в проекте с предыдущих домашних заданий.
Задача 1
Отображение базовых деталей
- Добавь новый
useState, в котором будет храниться вся задача целиком
- При выборе задачи справа должен появляться блок с деталями. В блоке с деталями выведи заголовок
задачи (
title) - Когда задача не выбрана, должен отображаться текст
"Task is not selected"
Итоговый результат 🚀

Задача 2
Загрузка детальной информации с сервера
- Реализуй загрузку детальной информации о задаче с сервера при клике на задачу
- Используй endpoint:
GET /boards/{boardId}/tasks/{taskId}для получения полной информации
boardId- Отобрази дополнительную информацию из детального запроса:
title- заголовок таскиboardTitle- заголовок доски в которой находится таскаdescription- описание таски. Еслиdescriptionотсутствует выведиno description
Итоговый результат 🚀. При клике на таску идёт запрос на сервер, приходит детальная информация и выводится в блоке деталей

Задача 3 ⭐
⭐ Дополнительное задание со звёздочкой. Проделывай по желанию.
Улучшение UX
- Проблема отзывчивости: При медленном интернете пользователь не понимает, что клик произошёл
- Реши эту проблему БЕЗ добавления нового состояния loading
- Подсказка: подумай о последовательности обновления состояний
- Индикация загрузки: Когда детали задачи загружаются, покажи "Loading..." вместо старых данных
- Используй только существующие состояния
- Подумай о логических условиях отображения
Итоговый результат 🚀. Для того чтобы проверить в Network выбери 3G

Удачи в выполнении домашнего задания! Помни: цель не просто заставить код работать, а понять принципы и научиться писать качественный, масштабируемый код.


