React с нуля: Основы современной фронтенд-разработки

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

1. Введение в React: JSX, виртуальный DOM и создание первых компонентов

Введение в React: JSX, виртуальный DOM и создание первых компонентов

Добро пожаловать в курс «React с нуля». Если вы здесь, значит, вы готовы перейти от верстки статических страниц и написания простых скриптов на jQuery или ванильном JavaScript к созданию мощных, масштабируемых веб-приложений. React — это не просто библиотека, это стандарт индустрии, который изменил подход к разработке пользовательских интерфейсов.

В этой первой статье мы разберем фундамент, на котором стоит React: компонентный подход, магию Virtual DOM и синтаксис JSX.

Что такое React и почему он так популярен?

React — это JavaScript-библиотека для создания пользовательских интерфейсов, разработанная компанией Facebook (ныне Meta). Ключевое слово здесь — библиотека. В отличие от фреймворков (как Angular), которые диктуют жесткие правила архитектуры всего приложения, React отвечает только за «View» (представление) — то, что пользователь видит на экране.

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

Компонентный подход

Представьте, что вы собираете конструктор LEGO. У вас есть кирпичики разных форм и цветов. Из маленьких кирпичиков вы собираете стену, из стен — дом, из домов — город. В React всё работает точно так же.

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

!Иерархическая структура компонентов React на примере страницы магазина

Виртуальный DOM (Virtual DOM)

Одной из главных проблем веб-разработки долгое время была производительность при работе с DOM (Document Object Model). Реальный DOM в браузере — это тяжелая структура. Когда вы меняете что-то на странице с помощью JavaScript (например, document.getElementById('app').innerHTML = ...), браузеру приходится заново пересчитывать стили, макет и перерисовывать элементы. Это медленно.

React решает эту проблему с помощью концепции Virtual DOM.

Виртуальный DOM — это легковесная копия реального DOM, хранящаяся в памяти в виде обычных JavaScript-объектов. Работа с ним происходит мгновенно, так как мы не затрагиваем браузерный API отрисовки.

Как происходит обновление (Reconciliation)

  • Рендеринг: Когда данные в приложении меняются, React создает новое дерево Виртуального DOM.
  • Сравнение (Diffing): React сравнивает новое дерево с предыдущей версией Виртуального DOM.
  • Обновление: Библиотека вычисляет минимально необходимый набор изменений и точечно применяет их к реальному DOM.
  • !Процесс согласования: React находит отличия и обновляет только измененные элементы

    Это позволяет React-приложениям работать невероятно быстро, даже если интерфейс очень сложный.

    Знакомство с JSX

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

    Это не строка и не HTML. Это JSX (JavaScript XML) — синтаксическое расширение для JavaScript. Оно позволяет писать структуру интерфейса так, как будто это HTML, но внутри JavaScript-файла.

    Браузеры не понимают JSX. Перед тем как код попадет в браузер, специальные инструменты (например, Babel) преобразуют его в обычные вызовы функций JavaScript.

    Пример того, во что превращается JSX:

    JSX:

    Результат компиляции (то, что видит браузер):

    Использовать JSX не обязательно, но это стандарт де-факто, так как он делает код читаемым и наглядным.

    Создание первого компонента

    В современном React компоненты — это просто JavaScript-функции. Функция должна возвращать JSX-разметку.

    Давайте создадим простой компонент:

    Чтобы использовать этот компонент, мы пишем его имя как HTML-тег:

    Обратите внимание: названия компонентов всегда должны начинаться с заглавной буквы. Если вы напишете <welcome />, React подумает, что это обычный HTML-тег, и ничего не сработает.

    Правила написания JSX

    JSX очень похож на HTML, но есть несколько строгих правил, которые нужно запомнить сразу.

    1. Вложенность в один родительский элемент

    Функция компонента не может вернуть несколько элементов, идущих подряд. Они должны быть обернуты в один родительский тег.

    Неправильно:

    Правильно:

    Если вы не хотите создавать лишний div в DOM-дереве, используйте Фрагмент (Fragment):

    2. Закрывайте все теги

    В HTML некоторые теги (например, <img>, <input>, <br>) можно не закрывать. В JSX все теги должны быть закрыты.

    3. camelCase для атрибутов

    Поскольку JSX — это JavaScript, некоторые слова зарезервированы. Самый частый пример — атрибут class. В JS слово class используется для создания классов, поэтому в JSX для стилей используется className.

    Также атрибуты, состоящие из нескольких слов, пишутся в верблюжьем регистре (camelCase):

    * class className * onclick onClick * tabindex tabIndex

    Пример:

    4. Вставка JavaScript выражений

    Самая мощная часть JSX — возможность вставлять любой JavaScript-код прямо в разметку. Для этого используются фигурные скобки {}.

    Внутри {} может быть переменная, функция или любое выражение, которое возвращает значение. Однако там нельзя использовать инструкции типа if, for или объявлять переменные (для условного рендеринга используется тернарный оператор, как в примере выше).

    Стилизация (краткий обзор)

    В React есть два основных способа добавить стили прямо в компонент:

  • Через классы (CSS): Вы используете className и подключаете обычный CSS-файл.
  • Inline-стили: Вы передаете объект стилей в атрибут style. Обратите внимание на двойные фигурные скобки: первые для входа в JS-режим, вторые — для самого объекта.
  • Заключение

    Мы сделали первый шаг в мир React. Теперь вы знаете, что:

    * React использует компоненты как кирпичики для построения интерфейса. * Virtual DOM обеспечивает высокую производительность, обновляя только то, что действительно изменилось. * JSX позволяет писать разметку внутри JavaScript, используя привычный синтаксис, но с силой программирования.

    В следующей статье мы углубимся в то, как делать компоненты «живыми» и интерактивными с помощью пропсов (props) и состояния (state).

    2. Интерактивность интерфейса: Хук useState и обработка событий

    Интерактивность интерфейса: Хук useState и обработка событий

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

    Сегодня мы вдохнем жизнь в наши приложения. Мы разберем, как React обрабатывает события, и познакомимся с одним из самых важных понятий в современной разработке — состоянием (State) и хуком useState.

    Обработка событий в React

    Если вы пришли из мира HTML и ванильного JavaScript, обработка событий в React покажется вам очень знакомой, но с парой важных отличий.

    В обычном HTML мы привыкли писать так:

    В React это выглядит немного иначе:

    Ключевые отличия

  • CamelCase именование: В React события именуются в верблюжьем регистре (camelCase), а не в нижнем. Вместо onclick мы пишем onClick, вместо onchangeonChange, onsubmitonSubmit.
  • Передача функции: В JSX вы передаете саму функцию как обработчик события, а не строку. Обратите внимание на фигурные скобки {activateLasers}.
  • Частая ошибка новичков

    Очень важно передавать функцию, а не вызывать её сразу. Посмотрите на разницу:

    * Правильно: onClick={handleClick} — мы говорим React: «Вызови эту функцию, когда пользователь кликнет». * Неправильно: onClick={handleClick()} — мы вызываем функцию прямо в момент отрисовки компонента, и результат её выполнения (обычно undefined) передается в onClick. Это приведет к тому, что функция сработает сразу при загрузке страницы, а не при клике.

    Пример простого компонента с кнопкой:

    Иногда нам нужно передать аргументы в обработчик. В этом случае мы используем стрелочную функцию:

    Проблема обычных переменных

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

    Если вы запустите этот код, то увидите, что в консоли значение count меняется, но на экране число остается равным 0. Почему так происходит?

  • Локальные переменные не сохраняются между рендерами. Когда React перерисовывает компонент, он вызывает функцию заново, и let count = 0 инициализируется снова.
  • Изменение переменной не запускает рендеринг. React не следит за обычными переменными. Он не знает, что данные изменились и нужно обновить интерфейс.
  • Чтобы решить эту проблему, нам нужно Состояние (State).

    Хук useState

    Состояние — это данные, которые меняются во времени и влияют на то, что отображается на экране. В функциональных компонентах для работы с состоянием используется специальная функция (хук) — useState.

    !Цикл обновления интерфейса: действие пользователя запускает изменение состояния, что приводит к перерисовке компонента.

    Синтаксис useState

    Чтобы использовать хук, его нужно импортировать из библиотеки react:

    Затем мы вызываем его внутри компонента:

    Давайте разберем эту строку детально:

  • useState(0): Мы вызываем хук и передаем ему начальное значение. В данном случае наш счетчик начинается с нуля.
  • const [count, setCount]: Хук возвращает массив из двух элементов. Мы используем деструктуризацию массива, чтобы присвоить эти элементы переменным.
  • * Первый элемент (count) — это текущее значение состояния. * Второй элемент (setCount) — это функция для обновления этого состояния.

    Теперь перепишем наш счетчик правильно:

    Что происходит при вызове setCount?

    Когда вы вызываете функцию обновления (в нашем случае setCount):

  • React запоминает новое значение.
  • React инициирует повторный рендеринг (re-render) компонента.
  • Функция Counter выполняется заново.
  • При повторном выполнении useState(0) React уже не берет ноль, а возвращает запомненное новое значение (например, 1).
  • React обновляет DOM, чтобы отобразить новое число.
  • Работа с полями ввода

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

    Рассмотрим пример, где мы выводим текст, который печатает пользователь:

    Здесь мы создаем цикл синхронизации:

  • Мы говорим инпуту: «Твое значение равно переменной text» (value={text}).
  • Когда пользователь пытается напечатать символ, срабатывает onChange.
  • Мы берем новый текст из event.target.value и обновляем состояние через setText.
  • React перерисовывает компонент, и инпут получает новое значение text.
  • Правила использования состояния

    Работа с useState кажется простой, но есть несколько подводных камней, о которых нужно знать.

    1. Состояние изолировано

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

    2. Обновление объектов и массивов

    Состояние в React должно быть неизменяемым (immutable). Это значит, что вы не должны менять объекты или массивы напрямую.

    Неправильно:

    Правильно: Нужно создать новый объект, скопировав старые данные. Для этого удобно использовать оператор расширения (spread operator) ....

    То же самое касается массивов. Вместо push (который мутирует массив), используйте создание нового массива:

    3. Функциональное обновление

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

    В таких случаях лучше передавать в функцию обновления не значение, а функцию-колбэк:

    Это надежный способ гарантировать, что вы работаете с самыми свежими данными.

    Заключение

    Сегодня мы сделали огромный шаг вперед. Теперь вы умеете:

    * Обрабатывать события с помощью onClick, onChange и других атрибутов. * Использовать хук useState для хранения данных, которые меняются со временем. * Понимать разницу между обычными переменными и состоянием React. * Создавать контролируемые компоненты для ввода данных.

    В следующей статье мы разберем, как работать со списками данных: как превратить массив объектов в список красивых карточек, используя метод map.

    3. Жизненный цикл и побочные эффекты: Работа с хуком useEffect и запросы к серверу

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

    В предыдущих статьях мы научились создавать компоненты, отображать их на экране и управлять их внутренним состоянием с помощью хука useState. Теперь наши приложения могут реагировать на действия пользователя, такие как клики и ввод текста. Но что, если нам нужно сделать что-то, что выходит за рамки простого обновления интерфейса?

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

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

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

    Функциональный компонент в React, в идеале, должен быть чистой функцией. Это значит, что он должен делать только две вещи:

  • Принимать входные данные (props).
  • Возвращать JSX (разметку).
  • Если компонент начинает «лезть» во внешний мир — менять DOM напрямую, отправлять сетевые запросы, подписываться на события окна браузера — это и есть побочный эффект. Мы не можем выполнять эти действия прямо в теле функции компонента, так как React вызывает эту функцию при каждой перерисовке. Представьте, что вы отправляете запрос на сервер каждый раз, когда пользователь нажимает любую кнопку. Это «положит» ваш сервер.

    Для безопасного выполнения таких действий React предоставляет хук useEffect.

    Жизненный цикл компонента

    Чтобы понять, когда запускать эффекты, нужно разобраться с жизненным циклом компонента. У каждого компонента есть три основные фазы «жизни»:

  • Монтирование (Mounting): Компонент создается и впервые добавляется в DOM. Это его «рождение».
  • Обновление (Updating): Компонент перерисовывается из-за изменения пропсов или состояния (state).
  • Размонтирование (Unmounting): Компонент удаляется из DOM. Это его «смерть».
  • !Диаграмма фаз жизненного цикла: рождение, обновление и удаление компонента

    Хук useEffect позволяет нам вклиниваться в эти этапы.

    Основы useEffect

    Синтаксис хука выглядит так:

    useEffect принимает два аргумента:

  • Функция-эффект: Код, который нужно выполнить.
  • Массив зависимостей (опционально): Список переменных, от которых зависит эффект.
  • Поведение useEffect кардинально меняется в зависимости от второго аргумента.

    Сценарий 1: Без массива зависимостей

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

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

    Сценарий 2: Пустой массив зависимостей

    Если передать пустой массив [], React запустит эффект только один раз — сразу после монтирования компонента. Это аналог метода componentDidMount в классовых компонентах.

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

    Сценарий 3: Массив с зависимостями

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

    Здесь мы говорим React: «Следи за userId. Если он поменялся, выполни функцию снова».

    Функция очистки (Cleanup)

    Иногда эффекты создают «хвосты», которые нужно убирать, когда компонент удаляется или перед повторным запуском эффекта. Примеры: таймеры (setInterval), подписки на события (addEventListener).

    Для этого функция-эффект может вернуть другую функцию — функцию очистки.

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

    Запросы к серверу (Data Fetching)

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

    При работе с сетью нам обычно нужно три состояния:

  • data: Сами данные.
  • loading: Флаг загрузки (чтобы показать спиннер).
  • error: Ошибка (если что-то пошло не так).
  • Пример компонента с запросом

    Важные моменты:

  • Не делайте сам useEffect асинхронным. Нельзя писать useEffect(async () => ...). Функция эффекта должна возвращать либо ничего, либо функцию очистки. Асинхронная функция возвращает Промис, что нарушает правила React. Поэтому мы создаем async функцию внутри и сразу вызываем её.
  • Обработка ошибок. Всегда используйте блок try...catch, чтобы приложение не падало при проблемах с сетью.
  • Индикация загрузки. Пользователь должен понимать, что происходит, пока данные летят по сети.
  • Частые ошибки

    Бесконечный цикл

    Если вы меняете состояние внутри эффекта без массива зависимостей, произойдет следующее:

  • Рендер.
  • Запуск эффекта.
  • Изменение стейта (setCount).
  • Новый рендер (из-за изменения стейта).
  • Запуск эффекта...
  • И так до зависания браузера. Всегда проверяйте массив зависимостей.

    Пропущенные зависимости

    Если вы используете переменную внутри эффекта, она обязана быть в массиве зависимостей (кроме функций изменения стейта типа setCount). Современные линтеры (ESLint) обычно подсказывают, если вы что-то забыли.

    Заключение

    Хук useEffect — это мощный инструмент, который соединяет реактивную природу React с императивным миром внешних API и браузерных событий.

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

    В следующей статье мы поговорим о том, как создавать свои собственные хуки (Custom Hooks), чтобы переиспользовать логику между компонентами и делать код чище.

    4. Управление сложным состоянием: Context API, useReducer и пользовательские хуки

    Управление сложным состоянием: Context API, useReducer и пользовательские хуки

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

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

    В этой статье мы перейдем на новый уровень владения React и разберем три мощных инструмента: Context API, хук useReducer и создание пользовательских хуков (Custom Hooks).

    Проблема: «Прокидывание пропсов» (Prop Drilling)

    Представьте, что у вас есть корневой компонент App, в котором хранится информация о текущем пользователе. А где-то глубоко в структуре, внутри Header -> UserProfile -> Avatar, нужно отобразить фотографию этого пользователя.

    Используя только пропсы, вам придется передавать объект user через все промежуточные компоненты, даже если им самим эти данные не нужны.

    Это называется Prop Drilling (сверление пропсов). Это делает код громоздким, сложным для поддержки и рефакторинга.

    !Сравнение передачи данных через пропсы (Prop Drilling) и использование Context API

    Context API: Телепорт для данных

    Context API позволяет передавать данные через дерево компонентов без необходимости передавать пропсы на промежуточных уровнях вручную. Это идеально подходит для «глобальных» данных: текущий пользователь, тема оформления (светлая/темная), язык интерфейса.

    Работа с контекстом состоит из трех шагов:

  • Создание контекста.
  • Предоставление данных (Provider).
  • Потребление данных (Consumer/useContext).
  • Шаг 1: Создание контекста

    Обычно контекст создают в отдельном файле.

    Шаг 2: Обертывание в Provider

    Компонент Provider (Поставщик) позволяет дочерним компонентам подписаться на изменения контекста. Он принимает проп value — данные, которые мы хотим передать.

    Шаг 3: Использование хука useContext

    Теперь любой компонент внутри Provider (неважно, как глубоко) может получить значение контекста.

    > Важно: Используйте Context API умеренно. Если вы просто хотите избежать передачи пропсов на один-два уровня, лучше использовать композицию компонентов (передачу компонентов как children). Контекст делает компоненты менее переиспользуемыми, так как привязывает их к определенному окружению.

    useReducer: Управление сложной логикой

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

    На помощь приходит useReducer. Этот хук вдохновлен библиотекой Redux и использует концепцию Reducer (редьюсер).

    Что такое Reducer?

    Редьюсер — это чистая функция, которая принимает два аргумента:

  • state: текущее состояние.
  • action: объект, описывающий, что нужно сделать.
  • Функция возвращает новое состояние.

    Формула работы редьюсера:

    Пример: Сложный счетчик

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

    Преимущества useReducer

    * Предсказуемость: Логика изменения состояния собрана в одном месте (функции reducer), а не размазана по разным обработчикам событий. * Тестируемость: Редьюсер — это обычная JS-функция, её легко протестировать отдельно от React-компонента.

    Пользовательские хуки (Custom Hooks)

    Один из главных принципов программирования — DRY (Don't Repeat Yourself / Не повторяйся). В React мы используем компоненты, чтобы не дублировать UI. А чтобы не дублировать логику, мы используем пользовательские хуки.

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

    Пример: Выносим логику загрузки данных

    В прошлой статье мы писали код для загрузки пользователей внутри компонента. Представьте, что нам нужно загружать товары, новости и комментарии в разных компонентах. Копировать тот useEffect каждый раз — плохая идея.

    Давайте создадим свой хук useFetch.

    Теперь наш компонент становится невероятно чистым:

    Правила пользовательских хуков

  • Название должно начинаться с use. Это сигнал для React (и для линтеров), что внутри функции используются хуки и к ней применяются правила хуков.
  • Используйте стандартные хуки внутри. Вся магия работает только потому, что вы вызываете useState, useEffect и другие хуки внутри своей функции.
  • Когда и что использовать?

    | Инструмент | Для чего подходит | | :--- | :--- | | useState | Простые данные (числа, строки), независимые друг от друга. | | useReducer | Сложные объекты состояния; когда новое состояние зависит от предыдущего; сложная бизнес-логика. | | Context API | Глобальные данные, необходимые многим компонентам на разной глубине (темы, язык, авторизация). | | Custom Hooks | Переиспользование логики (не UI) между разными компонентами. |

    Заключение

    Сегодня мы значительно расширили наш арсенал. Мы научились:

    * Избегать «сверления пропсов» с помощью Context API. * Управлять сложным состоянием предсказуемо через useReducer. * Писать чистый и переиспользуемый код, создавая свои Custom Hooks.

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

    5. Создание одностраничных приложений (SPA): React Router и оптимизация производительности

    Создание одностраничных приложений (SPA): React Router и оптимизация производительности

    Мы прошли долгий путь. От понимания того, как JSX превращается в HTML, до управления сложным глобальным состоянием через Context API и useReducer. Теперь у нас есть все кирпичики, чтобы построить дом. Но пока что наш дом — это одна большая комната. В реальных приложениях нам нужны разные страницы: «Главная», «О нас», «Каталог», «Профиль», «Корзина».

    В этой статье мы превратим наше приложение в полноценное SPA (Single Page Application) с навигацией, а также научимся делать его быстрым, даже если кода станет очень много.

    Что такое SPA и зачем нам Роутер?

    В классической веб-разработке (MPA — Multi Page Application) каждый переход по ссылке заставлял браузер отправлять запрос на сервер, скачивать новый HTML-файл, заново загружать все скрипты и стили. Экран моргал, а пользователь ждал.

    Single Page Application (SPA) работает иначе. У нас есть только один HTML-файл (index.html). Когда пользователь кликает по ссылке «Контакты», браузер не перезагружает страницу. Вместо этого JavaScript (наш React) просто подменяет одни компоненты на другие и меняет URL в адресной строке.

    !Сравнение жизненного цикла MPA и SPA: перезагрузка страницы против динамической подмены контента

    React сам по себе не умеет работать с адресной строкой. Для этого нам нужна библиотека React Router.

    Установка и настройка React Router

    React Router — это стандарт де-факто для маршрутизации в React. Давайте установим его:

    Подключение Router

    Чтобы роутинг заработал, нам нужно обернуть все наше приложение в компонент BrowserRouter. Обычно это делается в файле index.js или main.jsx.

    Настройка маршрутов

    Теперь в компоненте App мы можем определить, какой компонент показывать по какому адресу. Для этого используются компоненты Routes и Route.

    Теперь, если вы введете в браузере http://localhost:3000/about, React отрисует компонент <About />.

    Навигация: Link vs Тег A

    Это критически важный момент. Если вы создадите меню с обычными ссылками <a>, ваше SPA сломается.

    Вместо этого нужно использовать компонент Link из библиотеки React Router. Он меняет URL и обновляет интерфейс, но не перезагружает страницу.

    Если вам нужно стилизовать активную ссылку (например, подсветить пункт меню, на котором находится пользователь), используйте NavLink. Он автоматически добавляет класс active к ссылке, если текущий URL совпадает с to.

    Динамические маршруты

    Часто нам нужны страницы шаблона: профиль пользователя, карточка товара, статья блога. Мы не можем создавать отдельный <Route> для каждого товара. Нам нужны параметры.

    В React Router параметры обозначаются двоеточием:

    Теперь этот маршрут сработает для /users/1, /users/alex, /users/123.

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

    Этот id можно использовать, например, чтобы сделать запрос к серверу (вспомните useEffect из прошлых уроков) и получить данные конкретного человека.

    Программная навигация

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

    Для этого используется хук useNavigate.

    Оптимизация производительности

    С ростом приложения растет и размер JavaScript-файла (бандла), который пользователь должен скачать. Если ваше приложение весит 5 МБ, пользователь с медленным интернетом будет смотреть на белый экран несколько секунд. Это недопустимо.

    React предлагает встроенные механизмы для решения этой проблемы.

    Разделение кода (Code Splitting) и React.lazy

    Идея проста: не загружать код страницы, пока пользователь на нее не перешел. Зачем грузить код «Личного кабинета» и «Админки», если пользователь просто зашел на «Главную» почитать новости?

    Мы можем использовать React.lazy для динамического импорта компонентов.

    Обычный импорт (грузится сразу):

    Ленивый импорт (грузится по требованию):

    Однако, так как загрузка занимает время (пусть и доли секунды), React должен знать, что показывать в этот момент. Для этого используется компонент Suspense (подвешивание).

    Теперь браузер скачает код страницы About только тогда, когда пользователь кликнет на ссылку.

    Мемоизация: React.memo

    Еще одна проблема производительности — лишние перерисовки (re-renders). В React, если родительский компонент обновляется, все его дети тоже обновляются по умолчанию. Даже если их пропсы не изменились.

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

    React.memo — это компонент высшего порядка, который «запоминает» результат рендера компонента. Он проверяет: «Изменились ли пропсы?». Если нет — он не перерисовывает компонент, а берет прошлый результат из памяти.

    Теперь, если родитель обновится, но проп data останется прежним, ExpensiveComponent не будет перерисован.

    > Важно: Не нужно оборачивать в memo всё подряд. Сравнение пропсов тоже требует ресурсов. Используйте это только для тяжелых компонентов или тех, которые действительно часто перерисовываются без необходимости.

    Заключение

    Сегодня мы превратили набор разрозненных компонентов в настоящее приложение.

  • React Router позволил нам создать многостраничную структуру, сохраняя плавность SPA.
  • Link и useNavigate дали нам контроль над перемещением пользователя.
  • useParams позволил создавать гибкие шаблоны страниц.
  • React.lazy и Suspense помогли нам разбить приложение на части, чтобы оно загружалось мгновенно.
  • Это была последняя теоретическая статья базового курса. Теперь у вас есть полный набор инструментов: компоненты, пропсы, стейт, эффекты, контекст, роутинг и оптимизация. Этого достаточно, чтобы создать практически любое веб-приложение, от интернет-магазина до социальной сети.

    Впереди вас ждет практика и создание собственных проектов. Удачи в мире React!