Рефакторинг приложения "MusicFun": синхронизация компонентов
📚 Анализ исходного кода
1. Анализ компонентов
TracksList: "умный" (smart) компонент, который самостоятельно загружает данные через useEffect,
хранит их в локальном состоянии (local state), и отвечает за выделение выбранного трека.
TrackDetail: также является "умным" компонентом. Он использует useEffect для загрузки деталей
одного и того же трека, используя захардкоженный (жестко заданный) ID.
2. Основная проблема: компоненты не синхронизированы
TracksList и TrackDetail являются "детьми" (children) компонента MainPage и не могут
общаться напрямую. Когда пользователь выбирает трек в TracksList, TrackDetail продолжает
отображать тот трек, ID которого был захардкожен.
3. Решение: синхронизация через родительский компонент
Для синхронизации двух дочерних компонентов необходимо поднять состояние вверх, в их общего родителя
(MainPage).
Шаг 1: уведомление родителя о событии (callback)
1. Изменение в TracksList:
- В
TracksListпри клике на трек, помимо обновления своего локального состояния, вызывается функция изprops, например,props.onTrackSelected(track.id). - Чтобы избежать ошибок, если родитель не передал эту функцию, используется оператор опциональной
последовательности (
?.):props.onTrackSelected?.(track.id). Это позволяет безопасно вызвать функцию, только если она существует.
2. Изменение в MainPage:
- В
MainPageкомпонентTracksListпередается колбэк-функцияonTrackSelected, которая будет вызвана при выборе трека. - Чтобы
MainPageмог хранитьIDвыбранного трека и передавать его вTrackDetail, в нем создается локальное состояние с помощьюuseState:
- Теперь колбэк
onTrackSelectedобновляет это состояние:setSelectedTrackId(id).
Шаг 2: Передача данных второму дочернему компоненту (Props)
1. Изменение в MainPage:
- Когда состояние
selectedTrackIdвMainPageменяется,Reactперерисовывает этот компонент. - Во время перерисовки
MainPageпередает актуальное значениеselectedTrackIdвTrackDetailчерезprops:<TrackDetail trackId={selectedTrackId} />.
2. Изменение в TrackDetail:
TrackDetailначинает приниматьtrackIdизpropsи использует его для загрузки данных вместо захардкоженного значения.useEffectвTrackDetailтеперь зависит отprops.trackId, что заставляет его перезапускаться и загружать новый трек при изменении этогоID.
📚 Рефакторинг
После того как основная функциональность была реализована, был проведен рефакторинг для улучшения читаемости и структуры кода.
1. Контролируемый компонент
- Была рассмотрена идея сделать
TracksListконтролируемым компонентом. Локальное состояние дляselectedTrackIdбыло удалено изTracksList, и теперь он получаетIDвыбранного трека исключительно изpropsотMainPage. - Это устраняет дублирование состояния и делает
MainPageединственным источником правды о том, какой трек выбран.
2. Декомпозиция TracksList
- Элемент списка треков был вынесен в отдельный компонент
TrackItem.
- Это делает компонент
TracksListболее компактным и читаемым, так как он теперь отвечает только за отрисовку списка, а не за разметку каждого отдельного элемента.
3. Оптимизация и именование
- Именование колбэков: имя колбэка было изменено с
onTrackSelected(событие произошло) наonSelect(намерение/действие), что более точно отражает его суть в новой архитектуре. - Оптимизация колбэков: чтобы не создавать новую функцию для каждого
TrackItemпри каждой перерисовке,TrackItemтеперь сообщает родителю о выборе, передавая свойtrack.idв колбэкonSelect. Это позволяет создать одну функцию-обработчик для всех элементов списка.
4. Стиль кода
- Деструктуризация
props: вместо обращенияprops.trackIdиспользуется деструктуризация в параметрах функции:function TrackDetail({ trackId }). Это сокращает код, но может снижать читаемость для новичков. - Вынесение обработчиков: функции-обработчики событий (
handleClick,handleResetClickи т.д.) были вынесены из JSX-разметки в отдельные константы внутри компонента для улучшения чистоты кода.
Итоговый результат 🚀. Создано полноценное рабочее приложение с правильной архитектурой,
использующее props, колбэки, useEffect и useState. Проведен рефакторинг для улучшения
структуры и читаемости.
🏠 Домашнее задание
Цель задания: Научиться синхронизировать состояние между компонентами через callback-функции и освоить принципы декомпозиции компонентов.
По аналогии как видео необходимо доработать основное приложение trelly, над которым мы закончили
работать в 16 домашнем задании
Задание 1
Из компонента MainPage.tsx удали компоненты Header, PageTitle и Footer чтобы не отвлекаться.
В дальнейшем при работе с дизайном мы вернем данные компоненты
Задание 2
В компоненте TaskDetails.tsx selectedTaskId и boardId захардкоджены (не изменяются),
соответственно при выборе новой задачи у нас данные всегда остаются одинаковыми. Твоя задача
переписать код таким образом, чтобы детали трека при выборе менялись
- В
MainPage.tsxдолжно быть 2 стейта (для храненияselectedTaskIdиboardId) - При клике на таску в компоненте
TasksList.tsxпередавайselectedTaskIdиboardIdчерез callbacks вMainPage.tsx - Полученные на втором шаге
selectedTaskIdиboardIdпередавай в компонентTaskDetails.tsx
Итоговый результат 🚀

Задание 3
Создай компонент TaskItem.tsx и вынеси в него код который относится непосредственно к таске
- передавай свойство
isSelectedчтобы компонентTaskItem.tsxне думал о деталях как ему выделятьborder onTaskSelected- это callback-функция, которая вызовется при нажатии на таску и передаст родителю сразу 2 параметра:taskIdиboardId. ❗Если не понимаешь как это сделать, то можешь сделать 2 callback-функции, первая будет передаватьtaskId, а втораяboardId
В результате декомпозиции все должно работать как и на предыдущем шаге 🚀
Задание 4
В компоненте TasksList.tsx добавь кнопку сброса выделения (reset), при нажатии на которую должно
произойти следующее:
Borderвыделенного трека должен стать черным- Информация о треке в компоненте
TaskDetails.tsxдолжна сброситься, чтобы пользователь увиделTask is not selected
Итоговый результат 🚀

Задание 5
Пройдись по компонентам MainPage, TasksList, TaskItem, TaskDetails и вынеси функции из
разметки
В результате декомпозиции все должно работать как и на предыдущем шаге 🚀
Задание 6 🌟
Дополнительное задание (по желанию) 🌟
Используй деструктуризацию пропсов во всех компонентах
Плюсы и минусы деструктуризации пропсов в React
Плюсы
- Читаемость кода — код становится более чистым и понятным
- Меньше повторений — не нужно писать
props.перед каждым свойством - Явное указание используемых пропсов — сразу видно в сигнатуре функции, какие пропсы использует компонент
- Возможность задать значения по умолчанию — можно указать дефолтные значения прямо в параметрах
- Лучшая поддержка автодополнения — IDE лучше предлагает доступные свойства
- Улучшенная документированность — понятно, какие данные ожидает компонент
- Сокращение объема кода — меньше символов для написания
Минусы
- Потеря контекста — в больших компонентах неясно, откуда берется переменная
- Сложности с передачей всех пропсов дальше — нужен rest оператор
...restProps - Проблемы при рефакторинге — сложнее передать объект пропсов в другую функцию
- Длинные списки параметров — при большом количестве пропсов сигнатура становится громоздкой
- Усложнение условной деструктуризации — сложнее делать условную или вычисляемую деструктуризацию
- Потеря ссылки на объект props — иногда нужен доступ ко всему объекту пропсов
- Сложность динамического доступа — труднее обращаться к пропсам по вычисляемому ключу
В результате декомпозиции все должно работать как и на предыдущем шаге 🚀
🔶 Помни: все приложения мира — это в основном List-Detail. Освоив этот паттерн, ты сможешь браться за тестовые задания и понимать архитектуру большинства современных веб-приложений.
🔶 Экспериментируй с состоянием: попробуй оставить состояние в дочернем компоненте, попробуй поднять наверх. Поломай что-то специально — посмотри, что произойдёт. Только через эксперименты приходит понимание.
🔶 Осваивай современный синтаксис: деструктуризация, optional chaining, callback-функции — это не "синтаксический сахар", это инструменты профессионального разработчика.
🔶 Не останавливайся: трудно, тяжело — передохни 1-2 дня максимум и возвращайся. Работай утром, когда мозг свежий. Не сжигай дофамин на соцсети — береги энергию для сложного познания.


