Видеоуроки, интерактивный редактор и сохранение прогресса — бесплатно, сразу после входа.
ВойтиСоздать аккаунт — бесплатноЗакончили урок?
Войдите, чтобы отмечать прогресс
В этом решении мы разбиваем монолитное TODO приложение на несколько независимых компонентов для улучшения структуры кода.
Компонент TodoItem отвечает за отображение одной задачи. Он принимает объект todo с данными задачи и две функции-обработчика onToggle и onDelete. Компонент не знает, как именно работают эти функции - он просто вызывает их в нужный момент. Это называется "инверсия управления" - дочерний компонент не управляет логикой, а делегирует её родителю.
Мы не можем написать onChange={onToggle(todo.id)}, потому что это вызовет функцию сразу при рендере. Стрелочная функция создаёт новую функцию, которая будет вызвана только при событии. Внутри этой функции мы вызываем onToggle с нужным параметром todo.id. Это паттерн для передачи параметров в обработчики событий.
Если задача выполнена (todo.done === true), добавляем класс completed. CSS использует этот класс для зачёркивания текста или изменения цвета. Тернарный оператор выбирает между классом и пустой строкой.
Компонент TodoListView отвечает за отображение списка задач. Он проверяет, есть ли задачи - если есть, рендерит список TodoItem, если нет - показывает сообщение "Нет задач". Этот компонент получает функции onToggle и onDelete и передаёт их дальше в TodoItem. Это называется "пробрасывание props" (prop drilling).
Мы передаём каждую задачу в TodoItem вместе с функциями-обработчиками. Функции onToggle и onDelete приходят из родительского компонента App, проходят через TodoListView и попадают в TodoItem. Все три компонента используют одни и те же функции, но на разных уровнях вложенности.
Компонент TodoInput - это контролируемый компонент для ввода новой задачи. Он принимает текущее значение value, функцию для изменения onChange и функцию добавления onAdd. Состояние хранится в родительском компоненте App, а TodoInput только отображает его и сообщает об изменениях.
Когда пользователь вводит текст, срабатывает событие onChange. Мы извлекаем значение из e.target.value и передаём его в функцию onChange, которая пришла через props. В App эта функция - это setNewTodo, которая обновляет состояние.
Компонент TodoHeader отображает статистику - сколько задач выполнено из общего количества. Он принимает два числа через props и просто показывает их. Вычисления этих чисел происходят в App, а TodoHeader только отображает результат.
Компонент App собирает все части вместе. Он не содержит разметки с деталями - только композицию из четырёх компонентов. Каждый компонент получает необходимые данные и функции через props. Структура читается как книга - заголовок, список задач, поле ввода.
Всё состояние приложения живёт в App. Дочерние компоненты (TodoItem, TodoList, TodoInput, TodoHeader) не имеют своего состояния - они получают данные через props и вызывают callback-функции для изменений. Это паттерн "подъём состояния" - состояние находится на самом верхнем уровне, откуда оно нужно нескольким компонентам.
Все функции, изменяющие состояние, определены в App. Они вызывают setTodos для обновления массива задач. Эти функции передаются вниз через props, и дочерние компоненты вызывают их, когда пользователь совершает действие. Логика остаётся централизованной в одном месте.
Вместо хранения счётчиков в состоянии, мы вычисляем их каждый раз при рендере. Метод filter отбирает выполненные задачи, length даёт их количество. Эти значения автоматически обновляются при изменении todos - не нужно помнить об обновлении счётчиков при добавлении или удалении задач.
Мы передаём setNewTodo напрямую как prop onChange. Это работает, потому что setNewTodo - это функция, принимающая новое значение. В TodoInput мы вызываем onChange(e.target.value), что эквивалентно setNewTodo(e.target.value). Не нужно создавать обёртку-функцию, если сигнатуры совпадают.