Основы разработки на React.js

React.js — это популярная JavaScript-библиотека для создания пользовательских интерфейсов на основе компонентов и Virtual DOM [purpleschool.ru](https://purpleschool.ru/knowledge-base/react/basics/react-js). В этом курсе вы освоите современные подходы к разработке, сфокусировавшись на функциональных компонентах и хуках [codelab.pro](https://codelab.pro/kurs-po-react-2025-vvedenie-1/). Вы научитесь управлять состоянием, обрабатывать события и работать с формами для создания интерактивных веб-приложений [metanit.com](https://metanit.com/web/react).

1. Основы синтаксиса JSX и механизм рендеринга элементов через Virtual DOM

Основы синтаксиса JSX и механизм рендеринга элементов через Virtual DOM

Разработка пользовательских интерфейсов долгое время опиралась на строгое разделение технологий: структура описывалась в HTML, внешний вид — в CSS, а логика взаимодействия — в JavaScript. Однако с ростом сложности веб-приложений стало очевидно, что логика отображения неразрывно связана с самой разметкой. Библиотека React предложила принципиально иной подход, объединив разметку и логику в единые блоки — компоненты.

Фундаментом этого подхода стали две ключевые концепции: синтаксис JSX для описания интерфейса и механизм Virtual DOM для его эффективного отображения.

Декларативный подход к созданию интерфейсов

Прежде чем погружаться в синтаксис, важно понять философию React. Традиционный подход к работе с браузером (Vanilla JS) является императивным. Это значит, что разработчик должен пошагово описывать, как изменить интерфейс.

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

  • Найти элемент в документе по его идентификатору.
  • Прочитать его текущее текстовое значение.
  • Преобразовать текст в число.
  • Прибавить единицу.
  • Записать новое значение обратно в элемент.
  • React использует декларативный подход. Вы описываете, как должен выглядеть интерфейс в конкретном состоянии, а библиотека сама берет на себя задачу пошагового обновления экрана. Вы просто говорите: «Покажи счетчик со значением 5», и React делает так, чтобы на экране появилась пятерка.

    Синтаксис JSX: JavaScript с человеческим лицом

    Для реализации декларативного подхода создатели React разработали JSX (JavaScript XML) — расширение синтаксиса языка JavaScript. На первый взгляд JSX выглядит как обычный HTML-код, помещенный прямо внутрь скрипта:

    Этот странный тег не является ни строкой, ни фрагментом HTML. Это полноценное выражение JavaScript. JSX позволяет разработчикам писать HTML-подобный код непосредственно в JS-файлах, что делает структуру визуально понятной и читаемой.

    > React исходит из принципа, что логика рендеринга неразрывно связана с прочей логикой UI: с тем, как обрабатываются события, как состояние изменяется во времени и как данные готовятся к отображению. > > Официальная документация React

    Встраивание выражений в JSX

    Главная сила JSX заключается в том, что он наделен всей мощью JavaScript. Вы можете встраивать любые корректные JS-выражения прямо в разметку, используя фигурные скобки {}. Фигурные скобки работают как «окно» из мира разметки в мир программирования.

    Внутри скобок можно производить вычисления, вызывать функции или обращаться к свойствам объектов:

    Важно помнить, что внутри {} можно использовать только выражения (expressions) — код, который возвращает какое-либо значение (например, , вызов функции или тернарный оператор). Использовать инструкции (statements), такие как циклы for или блоки if, напрямую внутри разметки JSX нельзя.

    Как JSX превращается в JavaScript

    Браузеры не понимают синтаксис JSX. Если вы попытаетесь выполнить такой код напрямую, браузер выдаст синтаксическую ошибку. Перед тем как попасть в браузер, код JSX проходит через процесс компиляции (обычно с помощью инструмента Babel).

    Каждый тег JSX преобразуется в вызов специальной функции React.createElement().

    Например, этот код:

    После компиляции превращается в обычный JavaScript:

    Результатом выполнения этой функции является обычный JS-объект, который описывает, что должно появиться на экране. Этот объект называется React-элементом.

    Проблема прямого взаимодействия с DOM

    Чтобы понять, как React отображает эти элементы на экране, нужно разобраться с тем, как браузер работает с веб-страницами.

    Когда браузер загружает HTML-документ, он создает DOM (Document Object Model) — древовидную структуру объектов, представляющую все элементы страницы. Каждый тег становится узлом этого дерева.

    Взаимодействие с реальным DOM — это очень «дорогая» и медленная операция для компьютера. Если вы изменяете хотя бы один элемент (например, меняете цвет кнопки), браузеру часто приходится пересчитывать размеры и положение соседних элементов, а затем заново отрисовывать часть экрана.

    Представьте, что вы написали книгу и нашли в ней опечатку в одном слове. Прямая работа с DOM похожа на то, как если бы вы перепечатывали весь тираж книги из-за одной ошибки. Это крайне неэффективно. Намного логичнее было бы взять ластик, стереть одно слово и вписать правильное. Именно эту логику реализует React.

    Virtual DOM: легковесная копия интерфейса

    Для решения проблемы производительности React использует концепцию Virtual DOM (Виртуальный DOM).

    Virtual DOM — это просто копия реального DOM-дерева, сохраненная в оперативной памяти в виде обычных JavaScript-объектов. Поскольку это просто объекты в памяти, а не реальные пиксели на экране, операции с Virtual DOM происходят мгновенно.

    !Схема работы Virtual DOM

    Сравнение подходов

    | Характеристика | Реальный DOM | Virtual DOM | | :--- | :--- | :--- | | Скорость обновления | Медленная (требует перерисовки экрана) | Очень быстрая (только вычисления в памяти) | | Структура | Тяжелые объекты браузера | Легковесные JavaScript-объекты | | Обновление | Обновляет элементы напрямую | Вычисляет разницу и передает команды реальному DOM |

    Механизм рендеринга и алгоритм согласования (Reconciliation)

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

    Шаг 1: Первичный рендер (Initial Render)

    Когда приложение запускается впервые, React вызывает ваши компоненты, получает от них JSX и строит из него первое дерево Virtual DOM. Затем он берет это виртуальное дерево и целиком транслирует его в реальный DOM браузера. Пользователь видит интерфейс.

    Шаг 2: Обновление состояния

    Допустим, пользователь нажал на кнопку, и данные в приложении изменились. React реагирует на это изменение и заново вызывает компонент, в котором произошли изменения. Компонент возвращает новый JSX.

    Шаг 3: Создание нового Virtual DOM

    На основе нового JSX React строит новое дерево Virtual DOM. Теперь в памяти библиотеки находятся два дерева: старое (до клика) и новое (после клика).

    Шаг 4: Алгоритм Diffing (Поиск различий)

    На этом этапе в дело вступает алгоритм согласования (Reconciliation). React начинает попарно сравнивать узлы старого и нового виртуальных деревьев. Этот процесс называется Diffing (от английского difference — разница).

    !Интерактивная визуализация алгоритма согласования (Diffing)

    Алгоритм работает по строгим правилам для максимальной скорости:

  • Сравнение типов элементов: Если тип элемента изменился (например, тег <div> превратился в <span>), React не пытается их сравнивать. Он полностью удаляет старый узел из реального DOM и создает новый.
  • Сравнение атрибутов: Если тип элемента остался прежним (например, это всё тот же <img>), React сравнивает их атрибуты. Если изменился только атрибут src, React зафиксирует, что нужно обновить только ссылку на картинку, не трогая сам тег.
  • Шаг 5: Синхронизация с реальным DOM (Commit)

    После того как алгоритм Diffing составил точный список всех изменений, React переходит к фазе фиксации (Commit). Он обращается к реальному DOM браузера и применяет только найденные изменения.

    Возвращаясь к аналогии с рестораном: если вы заказали бургер, картошку и колу, а потом попросили заменить колу на сок, официант (Virtual DOM) не заставит повара выбрасывать готовый бургер и жарить новый. Он просто заменит напиток на подносе.

    Именно благодаря связке декларативного синтаксиса JSX и умного механизма Virtual DOM, разработчики могут писать код так, будто страница обновляется целиком каждую миллисекунду, в то время как React «под капотом» выполняет лишь точечные, микроскопические изменения в браузере, сохраняя высочайшую производительность приложения.

    2. Функциональные компоненты в React и передача данных через свойства (Props)

    Функциональные компоненты в React и передача данных через свойства (Props)

    Изучив синтаксис JSX и механизм работы Virtual DOM, мы поняли, как React создает и эффективно обновляет элементы на экране. Однако реальные веб-приложения состоят из сотен кнопок, форм, списков и карточек. Если писать весь интерфейс сплошным полотном кода, он быстро станет нечитаемым и сложным в поддержке.

    Чтобы решить эту проблему, React предлагает архитектуру, основанную на компонентах. Компонентный подход позволяет разбить сложный пользовательский интерфейс на независимые, изолированные и повторно используемые блоки.

    Что такое функциональные компоненты

    В современной разработке на React основным строительным блоком является функциональный компонент. Технически это обычная функция JavaScript, которая принимает данные и возвращает разметку JSX, описывающую, что должно появиться на экране.

    Представьте себе конструктор Lego. У вас есть стандартные детали: кубики, колеса, окна. Из них вы можете собрать машину, дом или космический корабль. В React функциональные компоненты — это ваши уникальные детали конструктора, из которых собирается приложение.

    Рассмотрим базовый пример создания компонента:

    Чтобы использовать этот компонент в другом месте приложения, мы пишем его имя в угловых скобках, как обычный HTML-тег:

    В этом примере компонент App (который обычно является корневым компонентом всего приложения) дважды отрисовывает компонент WelcomeMessage. На экране появятся два одинаковых блока с приветствием.

    > Важное правило: Имена пользовательских компонентов в React всегда должны начинаться с заглавной буквы. > Если вы назовете компонент welcomeMessage (со строчной буквы), React подумает, что это стандартный HTML-тег (как <div> или <span>), и выдаст ошибку, так как такого тега в HTML не существует.

    Эволюция: почему именно функции?

    Если вы будете читать старые статьи или код легаси-проектов, вы можете встретить классовые компоненты (созданные через class MyComponent extends React.Component). До 2019 года только классы могли хранить внутреннее состояние и управлять жизненным циклом.

    Однако классы делали код громоздким, требовали понимания сложного контекста this в JavaScript и усложняли повторное использование логики. С выходом версии React 16.8 появились Хуки (Hooks) — специальные функции, которые наделили функциональные компоненты всеми возможностями классов.

    Сегодня функциональные компоненты — это абсолютный стандарт индустрии. Они короче, понятнее и работают быстрее.

    | Характеристика | Функциональные компоненты | Классовые компоненты (устаревший подход) | | :--- | :--- | :--- | | Синтаксис | Обычная JS-функция | Класс ES6 с методом render() | | Объем кода | Минимальный, легко читать | Громоздкий, много шаблонного кода | | Работа с this | Не требуется | Обязательна, часто вызывает ошибки | | Актуальность | Современный стандарт | Поддерживаются для старого кода |

    Свойства (Props): передача данных в компоненты

    Компонент WelcomeMessage из примера выше статичен — он всегда выводит один и тот же текст. Но реальная сила компонентов заключается в их переиспользуемости с разными данными.

    Для передачи данных в React используется механизм Props (сокращение от properties — свойства).

    Если компонент — это функция, то props — это аргументы этой функции. Вы передаете данные в компонент точно так же, как передаете атрибуты в HTML-теги.

    Давайте создадим компонент карточки пользователя, который будет принимать имя и возраст:

    Обратите внимание: React автоматически собирает все переданные атрибуты в один JavaScript-объект и передает его в компонент под именем props.

    Теперь мы можем использовать этот компонент в родительском App, передавая разные данные:

    Результатом работы этого кода станут три разные карточки на экране, хотя мы написали разметку карточки всего один раз.

    Деструктуризация Props для чистоты кода

    Постоянно писать props.имя_свойства утомительно и делает код визуально перегруженным. Поскольку props — это обычный объект, современные разработчики используют синтаксис деструктуризации JavaScript прямо в параметрах функции.

    Тот же самый компонент UserCard можно переписать более элегантно:

    Мы сразу «распаковали» свойства name и age из объекта props. Код стал чище и сразу понятно, какие именно данные ожидает получить этот компонент.

    Однонаправленный поток данных

    Один из важнейших архитектурных принципов Reactоднонаправленный поток данных (Unidirectional Data Flow).

    Данные в приложении всегда передаются строго сверху вниз: от родительского компонента к дочерним.

    !Схема однонаправленного потока данных в React

    Представьте себе водопад. Вода (данные) может течь только сверху (от родителя) вниз (к детям). Дочерний компонент не может передать данные обратно наверх через props напрямую или передать их соседнему компоненту (брату/сестре).

    Из этого принципа вытекает строгое правило React:

    > Props доступны только для чтения (Read-only). > Компонент никогда не должен изменять собственные props.

    Если вы попытаетесь написать внутри компонента props.name = "Новое имя", React выдаст ошибку. Компонент должен вести себя как чистая функция по отношению к своим свойствам: получив одни и те же props, он всегда должен возвращать один и тот же результат, не меняя входные данные.

    Если интерфейс должен измениться (например, пользователь нажал кнопку), родительский компонент должен передать новые props, и тогда React автоматически перерисует дочерний компонент с новыми данными.

    !Интерактивный визуализатор передачи Props от родителя к дочерним компонентам

    Особенности передачи различных типов данных

    Через props можно передавать абсолютно любые типы данных, доступные в JavaScript, а не только строки.

  • Строки передаются в двойных кавычках: name="Анна"
  • Числа, логические значения (boolean), массивы и объекты передаются внутри фигурных скобок {}. Фигурные скобки говорят JSX: «Здесь начинается код JavaScript».
  • Пример передачи разных типов данных:

    Обратите внимание на передачу объекта settings={{ theme: "dark" }}. Здесь две пары фигурных скобок: внешние обозначают вставку JS-выражения в JSX, а внутренние — создание самого объекта.

    Передача функций через Props

    Поскольку в JavaScript функции являются объектами первого класса, их тоже можно передавать через props. Это основной способ, с помощью которого дочерний компонент может сообщить родителю о каком-то событии (например, о клике по кнопке).

    В этом сценарии родитель App передает функцию handleUserClick вниз по «водопаду» в компонент CustomButton. Когда пользователь кликает на кнопку, вызывается функция, которая физически находится в родительском компоненте. Это не нарушает однонаправленный поток данных, так как дочерний компонент просто вызывает то, что ему дали сверху, не меняя сами данные.

    Понимание того, как разбивать интерфейс на функциональные компоненты и связывать их с помощью props, является ключом к созданию масштабируемых и легко поддерживаемых приложений на React.

    3. Управление локальным состоянием компонента с использованием хука useState

    Управление локальным состоянием компонента с использованием хука useState

    В предыдущих материалах мы разобрали, как строить интерфейс из изолированных функциональных компонентов и передавать им данные сверху вниз с помощью свойств (props). Мы выяснили строгое правило: свойства доступны только для чтения. Компонент не имеет права изменять переданные ему props.

    Но как тогда создавать интерактивные приложения? Что делать, если пользователь вводит текст в поле, нажимает кнопку «Лайк» или открывает выпадающее меню? Интерфейс должен реагировать на эти действия и меняться. Для этого компоненту нужна собственная «память» — механизм, позволяющий хранить данные, изменять их и сообщать React о необходимости перерисовать экран.

    Этот механизм называется локальным состоянием (state).

    Что такое состояние компонента

    Представьте себе обычный смартфон. У него есть заводские характеристики: диагональ экрана, модель процессора, объем встроенной памяти. Эти данные неизменны с момента выпуска телефона на заводе. В мире React это аналогично props — данным, которые компонент получил при создании и не может изменить.

    Однако у смартфона есть и другие параметры: текущий уровень заряда батареи, громкость звонка, включен или выключен Wi-Fi. Эти параметры постоянно меняются в зависимости от действий пользователя или внешних факторов. Именно эти изменяемые данные и являются состоянием.

    | Характеристика | Свойства (Props) | Состояние (State) | | :--- | :--- | :--- | | Источник данных | Передаются от родительского компонента | Создаются и управляются внутри самого компонента | | Изменяемость | Только для чтения (неизменяемые) | Изменяемые (через специальные функции) | | Назначение | Настройка внешнего вида и базовых данных | Хранение текущих данных взаимодействия (ввод, клики, переключатели) |

    Знакомство с хуком useState

    Для работы с состоянием в функциональных компонентах React предоставляет специальные функции, которые называются хуками (hooks). Хуки позволяют «зацепиться» за внутренние механизмы React, такие как память или жизненный цикл, не прибегая к написанию сложного кода на основе классов.

    Самый важный и часто используемый хук — это useState. Он позволяет добавить локальное состояние в функциональный компонент.

    Чтобы использовать useState, его необходимо сначала импортировать из библиотеки React:

    Рассмотрим базовый синтаксис вызова этого хука:

    Эта короткая строка делает сразу несколько важных вещей. Давайте разберем её по частям.

    Функция useState принимает ровно один аргумент — начальное значение состояния. В нашем примере это число 0. Если бы мы делали переключатель темы, начальным значением могло бы быть слово "light", а для поля ввода — пустая строка "".

    В ответ useState всегда возвращает массив, состоящий ровно из двух элементов:

  • Текущее значение состояния (в нашем случае переменная count).
  • Функция-установщик, которая позволяет это состояние обновить (переменная setCount).
  • Запись с квадратными скобками слева от знака равенства — это синтаксис деструктуризации массива из JavaScript. Мы просто «распаковываем» массив, который вернул хук, и сразу даем понятные имена его элементам. По негласной конвенции разработчиков, функция-установщик всегда начинается с префикса set, за которым следует имя переменной состояния с заглавной буквы.

    Создание интерактивного счетчика

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

    Когда пользователь нажимает на кнопку, срабатывает событие onClick, которое вызывает нашу функцию handleIncrement. Внутри нее мы вызываем setCount(count + 1). Если текущий count был равен 0, мы передаем в функцию-установщик число 1.

    !Интерактивная визуализация работы хука useState: от нажатия кнопки до перерисовки интерфейса

    Механизм рендеринга при изменении состояния

    Крайне важно понимать, что именно происходит под капотом, когда вы вызываете функцию setCount.

    Вызов функции-установщика не просто меняет значение переменной в памяти. Это сигнал для React: «Данные этого компонента изменились, его внешний вид больше не актуален». Получив этот сигнал, React запускает процесс ререндеринга (повторной отрисовки).

  • React заново вызывает функцию компонента Counter().
  • При повторном вызове хук useState(0) уже не использует начальное значение 0. Он «вспоминает», что в прошлый раз мы сохранили туда 1, и возвращает массив [1, setCount].
  • Компонент возвращает новый JSX, где в заголовке теперь написано «Текущий счет: 1».
  • React сравнивает новый Virtual DOM со старым (тот самый алгоритм Diffing, который мы изучали в первой статье) и обновляет только изменившуюся текстовую ноду в реальном браузере.
  • !Схема цикла обновления компонента: Действие -> Обновление State -> Ререндер -> Обновление DOM

    Золотое правило: состояние неизменяемо напрямую

    Начинающие разработчики часто совершают критическую ошибку, пытаясь изменить переменную состояния напрямую, как обычную переменную в JavaScript:

    Если вы напишете такой код, значение переменной count в памяти действительно изменится. Но на экране ничего не произойдет. Почему? Потому что React не узнает об этом изменении. Без вызова функции setCount сигнал о необходимости ререндеринга не отправляется.

    > Состояние в React должно восприниматься как доступное только для чтения. > Единственный легальный способ изменить состояние — использовать функцию-установщик, которую вернул хук useState.

    Обновление состояния на основе предыдущего значения

    Иногда новое значение состояния зависит от его текущего значения. В нашем примере со счетчиком мы писали setCount(count + 1). В большинстве случаев это работает отлично. Но есть нюанс, связанный с тем, как React оптимизирует производительность.

    Обновления состояния в React могут быть асинхронными. Если вы вызовете setCount несколько раз подряд в рамках одного действия, React может объединить (сгруппировать) эти вызовы для экономии ресурсов.

    Посмотрите на этот код:

    Ожидается, что счетчик увеличится на 2. Но на практике, если count был равен 0, обе функции увидят count как 0, и обе выполнят setCount(0 + 1). В итоге счетчик увеличится только на 1.

    Чтобы решить эту проблему и гарантированно работать с самым свежим состоянием, в функцию-установщик можно передать не готовое значение, а функцию обратного вызова (callback). Эта функция автоматически получит самое актуальное предыдущее состояние в качестве аргумента.

    Теперь код работает как часы. Первый вызов берет 0 и делает из него 1. Второй вызов берет уже обновленную 1 и делает из нее 2. Использование функции внутри установщика — это лучшая практика (best practice), если новое состояние вычисляется математически из старого.

    Работа с различными типами данных

    Хук useState не ограничен числами. В нем можно хранить любые типы данных JavaScript: строки, логические значения (boolean), массивы и даже сложные объекты.

    Строки: управление полями ввода

    Одно из самых частых применений состояния — это чтение того, что пользователь печатает в текстовое поле. В React такие поля называются управляемыми компонентами (controlled components).

    Здесь мы привязали значение поля ввода (value) к нашей переменной состояния name. А на событие onChange (которое срабатывает при каждом нажатии клавиши) мы повесили функцию, которая берет текст из поля (event.target.value) и обновляет состояние. В результате абзац с приветствием обновляется мгновенно, буква за буквой.

    Логические значения: переключатели интерфейса

    Булевы значения идеально подходят для скрытия или показа элементов, открытия модальных окон или переключения тем.

    В этом примере мы используем логическое отрицание !isOpen. Если спойлер был закрыт (false), он станет открытым (true), и наоборот. Конструкция {isOpen && <p>...<p>} — это стандартный паттерн условного рендеринга в React. Абзац появится в DOM-дереве только в том случае, если isOpen равно true.

    Несколько состояний в одном компоненте

    Если компоненту нужно отслеживать несколько независимых параметров, вам не нужно пытаться запихнуть их все в один сложный объект. Вы можете (и должны) вызывать хук useState столько раз, сколько вам нужно.

    Такой подход делает код чище, а логику обновления каждого отдельного кусочка данных — независимой и предсказуемой.

    Овладев хуком useState, вы сделали огромный шаг от создания статичных картинок к разработке по-настоящему живых веб-приложений. Состояние — это сердце любого интерактивного компонента.

    4. Обработка побочных эффектов и жизненный цикл компонента с хуком useEffect

    Обработка побочных эффектов и жизненный цикл компонента с хуком useEffect

    В прошлой статье мы научились наделять компоненты собственной памятью с помощью хука useState. Мы узнали, что изменение локального состояния — это сигнал для React к обновлению интерфейса. Однако компоненты редко существуют в полной изоляции. Им нужно получать данные с сервера, подписываться на события браузера, запускать таймеры или взаимодействовать с элементами вне React.

    Все эти действия выходят за рамки простого преобразования данных в интерфейс. В мире функционального программирования и React они называются побочными эффектами (side effects). Для безопасной и предсказуемой работы с ними существует специальный инструмент — хук useEffect.

    Что такое побочный эффект?

    В идеальном мире React каждый компонент должен быть чистой функцией. Чистая функция работает по строгому правилу: при одинаковых входных данных (свойствах и состоянии) она всегда возвращает один и тот же результат (JSX-разметку) и не меняет ничего за пределами своей области видимости.

    Представьте повара в ресторане. Его задача — взять ингредиенты (свойства) и приготовить блюдо (интерфейс). Это чистый процесс. Но если в процессе готовки повар звонит поставщику, чтобы заказать еще помидоров, или выходит в зал, чтобы включить кондиционер — это уже побочные эффекты. Они влияют на внешний мир.

    В веб-разработке к побочным эффектам относятся:

  • Отправка сетевых запросов к API (загрузка списка товаров, сохранение профиля).
  • Прямое манипулирование реальным DOM (например, изменение заголовка вкладки браузера document.title).
  • Установка таймеров и интервалов (setTimeout, setInterval).
  • Подписка на глобальные события (например, отслеживание прокрутки страницы или изменения размера окна).
  • Если мы попытаемся выполнить эти действия прямо внутри тела компонента, мы столкнемся с серьезными проблемами. React может вызывать функцию компонента множество раз для вычисления изменений. Если при каждом таком вызове будет отправляться запрос на сервер, мы мгновенно перегрузим сеть и сломаем приложение.

    Знакомство с хуком useEffect

    Хук useEffect позволяет изолировать побочные эффекты от процесса рендеринга. Он говорит React: «Отрисуй интерфейс, покажи его пользователю, и только после этого выполни мою функцию».

    Синтаксис хука выглядит следующим образом:

    Жизненный цикл: Render -> Commit -> Effect

    Чтобы понять, когда именно срабатывает useEffect, нужно вспомнить фазы работы React:

  • Фаза рендеринга (Render): React вызывает функцию компонента, вычисляет новый Virtual DOM и сравнивает его со старым.
  • Фаза фиксации (Commit): React вносит изменения в реальный DOM браузера.
  • Фаза эффектов (Effect): Браузер отрисовывает обновленный экран, и сразу после этого React запускает функции, переданные в useEffect.
  • Такая последовательность гарантирует, что тяжелые сетевые запросы или сложные вычисления не заблокируют отображение интерфейса. Пользователь мгновенно увидит визуальные изменения, а данные подгрузятся в фоновом режиме.

    !Схема жизненного цикла компонента: от вызова функции до запуска побочных эффектов

    Массив зависимостей: контроль над эффектами

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

    Для управления частотой вызовов хук принимает второй, необязательный аргумент — массив зависимостей (dependency array).

    В зависимости от того, что мы передадим в этот массив, поведение хука кардинально изменится. Существует три сценария:

    1. Массив не передан (запуск после каждого рендера)

    Если второй аргумент отсутствует, эффект будет выполняться после первоначального появления компонента на экране и после каждого его обновления (изменения состояния или свойств).

    2. Пустой массив (запуск один раз при монтировании)

    Если передать пустой массив [], мы сообщаем React: «Этот эффект ни от чего не зависит. Выполни его только один раз, когда компонент впервые появится на экране (смонтируется)». Это идеальное место для первоначальной загрузки данных.

    3. Массив с переменными (запуск при изменении конкретных данных)

    Если поместить в массив переменные (состояние или свойства), React будет сравнивать их текущие значения с теми, что были при предыдущем рендере. Эффект запустится только в том случае, если хотя бы одна переменная из массива изменилась.

    !Интерактивная демонстрация работы массива зависимостей. Попробуйте изменить состояние и посмотрите, сработает ли эффект

    Очистка эффектов (Cleanup)

    Некоторые побочные эффекты требуют «уборки» за собой. Представьте, что вы оформили платную подписку на журнал. Если вы переезжаете в другой город, вам нужно отменить подписку, иначе журналы продолжат приходить на старый адрес, а деньги будут списываться.

    В React «переезд» — это удаление компонента с экрана (размонтирование). Если компонент создал таймер, открыл соединение с сервером (WebSocket) или добавил слушатель событий к окну браузера, он обязан удалить их перед своим исчезновением. Иначе возникнет утечка памяти (memory leak), и приложение начнет тормозить.

    Для очистки эффекта функция внутри useEffect должна вернуть другую функцию. React автоматически вызовет эту возвращаемую функцию перед тем, как компонент будет удален, а также перед каждым повторным запуском этого же эффекта.

    Рассмотрим пример с таймером:

    Если пользователь перейдет на другую страницу и компонент Timer исчезнет, React вызовет clearInterval(intervalId). Без этой очистки таймер продолжал бы работать в фоне, пытаясь обновить состояние несуществующего компонента, что привело бы к ошибке.

    Частая ошибка: бесконечный цикл

    Одна из самых болезненных ошибок новичков при работе с useEffect — создание бесконечного цикла обновлений. Это происходит, когда эффект обновляет состояние, от которого сам же и зависит.

    Посмотрите на этот опасный код:

    Что здесь произойдет?

  • Компонент рендерится со значением count = 0.
  • Выполняется useEffect.
  • Внутри эффекта вызывается setCount(1).
  • Изменение состояния заставляет React снова отрендерить компонент.
  • После нового рендера снова запускается useEffect.
  • Вызывается setCount(2)... и так до бесконечности, пока браузер не зависнет.
  • Чтобы избежать этого, всегда внимательно следите за массивом зависимостей. Если эффект должен выполниться один раз — используйте []. Если он зависит от данных — укажите их явно, но убедитесь, что обновление этих данных внутри эффекта не приведет к неконтролируемой цепной реакции.

    Хук useEffect — это мост между декларативным миром React и императивным внешним миром браузера и серверов. Правильное понимание фаз рендеринга, массива зависимостей и механизма очистки позволит вам создавать надежные приложения, которые эффективно работают с данными и не расходуют лишние ресурсы устройства.

    5. Создание контролируемых компонентов и обработка данных из HTML-форм

    Любое интерактивное веб-приложение требует обмена информацией с пользователем. Регистрация аккаунта, оформление заказа в интернет-магазине, публикация комментария или фильтрация списка товаров — все эти действия опираются на HTML-формы.

    В традиционной веб-разработке браузер берет на себя всю работу по управлению формами. Когда пользователь вводит текст в поле <input>, браузер самостоятельно запоминает это значение в своей внутренней памяти (в реальном DOM). Если разработчику нужно получить эти данные, он вынужден обращаться к DOM-элементу напрямую и «вытягивать» из него значение в момент отправки формы.

    В React такой подход нарушает базовый принцип: интерфейс должен быть отражением состояния. Если браузер хранит данные формы в DOM, а React хранит данные приложения в useState, возникает рассинхронизация. Чтобы избежать этого, в React используется паттерн контролируемых компонентов (controlled components).

    Концепция контролируемого компонента

    Контролируемый компонент — это элемент формы (например, <input>, <textarea> или <select>), значение которого полностью управляется состоянием React, а не внутренним поведением браузера.

    В этом подходе React становится единственным источником истины (single source of truth). Поле ввода не имеет собственной независимой памяти. Оно лишь отображает то, что записано в переменной состояния, и сообщает об изменениях через обработчик событий.

    > Контролируемый компонент работает как диктор новостей: он не придумывает текст сам, а лишь озвучивает то, что написано на телесуфлере (в состоянии). Если текст на суфлере меняется, диктор мгновенно меняет свою речь.

    Рассмотрим базовый пример создания контролируемого текстового поля:

    Как работает этот механизм шаг за шагом:

  • Компонент рендерится впервые. Переменная searchQuery равна пустой строке ''.
  • Атрибут value={searchQuery} жестко привязывает текст внутри <input> к состоянию. Поле пустое.
  • Пользователь нажимает клавишу «А».
  • Срабатывает событие onChange, которое вызывает функцию handleInputChange.
  • В функцию передается объект события event. Внутри него находится путь event.target.value, который содержит новый текст, предложенный браузером («А»).
  • Вызывается функция setSearchQuery('А').
  • Изменение состояния заставляет React запустить повторный рендер компонента.
  • При новом рендере searchQuery равно «А». Атрибут value обновляется, и пользователь видит букву «А» на экране.
  • Весь этот цикл происходит за миллисекунды при каждом нажатии клавиши.

    !Схема однонаправленного потока данных в контролируемом компоненте

    Преимущества полного контроля

    Зачем писать дополнительный код для каждого поля ввода, если браузер умеет делать это сам? Контроль над состоянием в реальном времени открывает мощные возможности для разработчика.

    | Возможность | Описание | Пример из жизни | | :--- | :--- | :--- | | Мгновенная валидация | Проверка данных до их отправки на сервер | Подсветка поля красным, если пароль слишком короткий | | Форматирование ввода | Автоматическое изменение текста прямо во время набора | Приведение номера кредитной карты к формату 0000 0000 0000 0000 | | Условный рендеринг | Блокировка кнопок или показ скрытых полей на основе ввода | Кнопка «Оплатить» неактивна, пока не заполнено поле адреса |

    Представим, что нам нужно запретить пользователю вводить цифры в поле «Имя». С контролируемым компонентом это решается одной строкой в обработчике:

    Если пользователь нажмет клавишу с цифрой, условие не выполнится, состояние не обновится, и цифра просто не появится в поле ввода. React проигнорирует попытку браузера изменить текст.

    !Интерактивная демонстрация контролируемого компонента

    Управление сложными формами

    Если в форме одно или два поля, создание отдельных переменных useState для каждого из них выглядит логично. Но что делать, если форма содержит 10 полей (имя, фамилия, email, телефон, адрес и так далее)? Создавать 10 хуков состояния и 10 обработчиков onChange — значит нарушить принцип чистоты и читаемости кода.

    Вместо этого мы можем использовать один объект состояния для всей формы.

    В этом примере используется важный трюк. Каждому полю ввода назначен атрибут name, который строго совпадает с ключом в объекте formData.

    Когда срабатывает handleChange, мы извлекаем имя поля и его новое значение с помощью деструктуризации: const { name, value } = event.target. Затем мы используем синтаксис вычисляемых свойств объекта [name]: value, чтобы обновить только то поле, которое было изменено, сохранив остальные данные с помощью оператора расширения ...prevData.

    Особенности разных типов полей

    Большинство полей ввода (текст, пароль, email) работают с атрибутом value. Однако в HTML существуют элементы, которые передают данные иначе. React стандартизирует работу с ними, но требует небольших корректировок в коде.

    Чекбоксы (Checkboxes)

    Чекбокс не имеет текстового значения. Его состояние — это логическое значение: включен или выключен. Поэтому вместо атрибута value используется атрибут checked, а в объекте события мы обращаемся к event.target.checked.

    Выпадающие списки (Select)

    В классическом HTML для выбора элемента в <select> нужно было добавлять атрибут selected к конкретному тегу <option>. В React этот процесс упрощен: атрибут value вешается на сам тег <select>, что делает его поведение идентичным обычному текстовому полю.

    Отправка данных формы

    Сбор данных в состоянии — это лишь половина задачи. В конечном итоге эти данные нужно отправить на сервер или передать другому компоненту. Для этого используется событие onSubmit, которое вешается на сам тег <form>, а не на кнопку.

    По умолчанию, когда пользователь нажимает кнопку с типом submit внутри формы, браузер пытается отправить данные на текущий URL и перезагружает страницу. В одностраничных приложениях (SPA) на React перезагрузка страницы уничтожит все текущее состояние приложения.

    Чтобы предотвратить это стандартное поведение, необходимо вызвать метод preventDefault() у объекта события.

    Использование математических и логических операторов для валидации — стандартная практика. Например, проверка длины пароля , где — количество символов, легко реализуется благодаря тому, что данные всегда актуальны и находятся в переменной состояния.

    Контролируемые компоненты требуют написания чуть большего объема шаблонного кода по сравнению с традиционным HTML. Однако этот подход гарантирует предсказуемость интерфейса. Состояние React и то, что видит пользователь на экране, всегда синхронизированы, что исключает целый класс ошибок, связанных с потерей или искажением данных при заполнении форм.