React с нуля: от вёрстки до архитектуры и React Query

Практический курс по созданию приложения с этапа вёрстки до настройки роутинга и управления состоянием через Redux Toolkit [purpleschool.ru](https://purpleschool.ru/course/react-redux). Включает работу с API через Axios, кэширование данных с React Query и финальный рефакторинг кода по современным стандартам [wiki.merionet.ru](https://wiki.merionet.ru/merion-academy/course/frontend-razrabotka-na-react).

1. Вёрстка интерфейса и создание базовых компонентов React

Вёрстка интерфейса и создание базовых компонентов React

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

Смена парадигмы: от страниц к компонентам

В классической веб-разработке мы привыкли мыслить страницами: у нас есть index.html, about.html, и мы подключаем к ним стили и скрипты. React меняет этот подход. Вместо страниц мы строим интерфейс из независимых, переиспользуемых блоков — компонентов.

Согласно документации Next.js, компоненты можно представить как кирпичики LEGO: вы берете отдельные элементы и объединяете их для создания более крупных структур. > Если нужно обновить часть интерфейса, можно изменить конкретный компонент или «кирпичик». > > ru.nextjs.im

Императивный vs Декларативный подход

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

Императивный (как в jQuery или ванильном JS): Вы говорите браузеру, как* сделать что-то шаг за шагом. «Найди элемент с ID btn, добавь ему класс active, измени текст на 'Загрузка'». Декларативный (React): Вы описываете, что* хотите видеть в зависимости от данных. «Если isLoading равно true, покажи кнопку с текстом 'Загрузка' и классом active».

React берет на себя сложную работу по обновлению DOM (Document Object Model), чтобы привести интерфейс в соответствие с вашим описанием.

Настройка окружения

Для начала работы нам понадобится Node.js. Раньше стандартом был create-react-app, но сегодня индустрия перешла на Vite — это инструмент сборки, который работает значительно быстрее.

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

После запуска вы увидите локальный адрес (обычно http://localhost:5173), где отображается ваше приложение.

JSX: HTML внутри JavaScript

React использует синтаксис JSX (JavaScript XML). На первый взгляд это похоже на HTML, который мы пишем прямо внутри JavaScript-файла. Однако браузеры не понимают JSX, поэтому инструменты сборки (например, Vite) трансформируют его в обычный JavaScript перед тем, как код попадет в браузер.

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

  • Вложенность: Компонент должен возвращать один родительский элемент. Если вам не нужен лишний div в DOM-дереве, используйте Фрагмент: <> ... </>.
  • Атрибуты: В JS слово class зарезервировано, поэтому в JSX мы используем className.
  • Закрытие тегов: Все теги должны быть закрыты. Тег <img> в HTML может быть открытым, но в JSX он обязан выглядеть как <img />.
  • CamelCase: События и многие атрибуты пишутся в верблюжьем регистре: onClick, tabIndex.
  • Интерполяция данных

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

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

    Анатомия React-компонента

    Современный React использует функциональные компоненты. Это обычные JS-функции, которые возвращают JSX.

    Важное правило: Название функции компонента обязательно должно начинаться с заглавной буквы. React использует это, чтобы отличать ваши компоненты (например, <UserCard />) от встроенных HTML-тегов (например, <div>).

    Пример базового компонента:

    Теперь этот компонент можно импортировать и использовать в другом файле, например в App.jsx:

    Props: Передача данных

    Компоненты были бы бесполезны, если бы они всегда рендерили одно и то же. Чтобы сделать их динамическими, мы используем Props (пропсы/свойства). Это данные, которые родительский компонент передает дочернему. Они работают аналогично атрибутам в HTML.

    Пропсы доступны в первом аргументе функции-компонента. Обычно мы сразу деструктурируем этот объект для удобства.

    Создадим компонент карточки товара ProductCard:

    Использование компонента с разными данными:

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

    State: Добавляем интерактивность

    Пропсы позволяют настраивать компонент извне, но что если компонент должен меняться сам в ответ на действия пользователя? Например, счетчик лайков или раскрывающееся меню. Для этого используется State (состояние).

    В React переменные не работают так, как вы привыкли. Если вы создадите переменную let count = 0 и измените её по клику, React не узнает об этом и не перерисует интерфейс. Чтобы React отреагировал на изменение данных, нужно использовать хук useState.

    Хук useState

    useState — это функция, которая возвращает массив из двух элементов:

  • Текущее значение состояния.
  • Функцию для обновления этого значения.
  • Когда вы вызываете setCount, React понимает, что данные изменились, запускает функцию компонента заново, вычисляет новый JSX и обновляет только нужную часть DOM в браузере.

    Рендеринг списков

    В реальных приложениях мы редко вставляем компоненты по одному вручную. Обычно данные приходят в виде массива объектов (например, от API). Для отображения списков в React используется метод массива .map().

    Предположим, у нас есть массив товаров:

    Мы можем превратить этот массив данных в массив компонентов:

    Зачем нужен атрибут key?

    Обратите внимание на атрибут key={product.id}. Это обязательное требование React при рендеринге списков. Ключ должен быть уникальным идентификатором элемента среди соседей.

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

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

    Стилизация компонентов

    В React есть несколько способов стилизации. В рамках этого курса мы начнем с самого простого и надежного — CSS Modules или обычного CSS.

    При использовании обычного импорта (import './App.css') стили становятся глобальными, что может привести к конфликтам имен классов. Поэтому хорошей практикой является использование CSS Modules.

  • Создайте файл Button.module.css.
  • Напишите обычный CSS: .btn { background: blue; }.
  • Импортируйте его в компонент: import styles from './Button.module.css'.
  • Используйте как объект: <button className={styles.btn}>...</button>.
  • Это гарантирует, что стили кнопки не повлияют на другие элементы, так как сборщик автоматически сгенерирует уникальные имена классов (например, _btn_x7z1a).

    Итоги

    Мы рассмотрели базу, необходимую для старта разработки на React. Главное — научиться декомпозировать интерфейс на независимые части.

    * Компоненты — это функции, возвращающие JSX. Они позволяют разбивать интерфейс на переиспользуемые блоки. * JSX требует соблюдения правил: className вместо class, закрытие всех тегов и наличие одного родительского элемента. * Props используются для передачи данных от родителя к ребенку (только чтение). * State (useState) позволяет компонентам хранить и изменять собственные данные, реагируя на действия пользователя. * Списки рендерятся через .map(), и каждому элементу обязательно нужен уникальный key.

    2. Настройка маршрутизации и глобального состояния с Redux Toolkit

    Настройка маршрутизации и глобального состояния с Redux Toolkit

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

    По мере роста приложения возникают две фундаментальные проблемы:

  • Навигация: Как переключаться между экранами (например, «Главная», «Каталог», «Корзина») без перезагрузки страницы?
  • Глобальное состояние: Как передать данные (например, информацию о текущем пользователе или список товаров в корзине) из «шапки» сайта в глубоко вложенный компонент, не передавая их через 10 промежуточных уровней?
  • В этой статье мы решим эти задачи, внедрив React Router для маршрутизации и Redux Toolkit для управления глобальным состоянием.

    Клиентская маршрутизация: SPA подход

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

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

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

    Стандартом индустрии для маршрутизации в React является библиотека react-router-dom. Установим её:

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

    Определение маршрутов

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

    Обратите внимание на путь *. Это «wildcard» (джокер), который сработает, если ни один другой маршрут не подошел. Это идеальное место для 404-страницы.

    Навигация без перезагрузки

    Если вы используете обычный тег <a href="/catalog">, браузер выполнит жесткую перезагрузку страницы. Чтобы сохранить преимущества SPA, используйте компонент Link.

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

    Часто нам нужно создать страницу для конкретного товара, например /catalog/123, где 123 — это ID товара. В React Router для этого используется двоеточие:

    Внутри компонента ProductPage мы можем получить этот ID с помощью хука useParams:

    Глобальное состояние с Redux Toolkit

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

    Согласно документации PurpleSchool, Redux решает эту проблему, предоставляя единое хранилище данных. > Redux помогает “централизовать” состояние приложения — то есть сделать так, чтобы все важные данные хранились в одном месте (state store), а изменения этого состояния были прозрачными и контролируемыми. > > purpleschool.ru

    Почему Redux Toolkit (RTK)?

    Классический Redux требовал написания огромного количества шаблонного кода (boilerplate). Redux Toolkit — это официальный, рекомендуемый способ использования Redux сегодня. Он включает в себя лучшие практики «из коробки», упрощает настройку хранилища и работу с асинхронными запросами.

    Основные концепции Redux

    Работу Redux можно описать математически как функцию изменения состояния. Пусть — текущее состояние, а — действие (action), которое произошло.

    Где: * — новое состояние приложения. * — функция-редьюсер (reducer), которая содержит логику изменений. * — текущее состояние. * — объект действия, описывающий, что именно случилось (например, { type: 'cart/add', payload: product }).

    Важно понимать: в Redux мы никогда не меняем состояние напрямую. Мы «диспатчим» (отправляем) действие, а редьюсер вычисляет новое состояние.

    Настройка Redux Toolkit шаг за шагом

    Для начала установим необходимые пакеты:

    1. Создание Слайса (Slice)

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

    Создадим файл src/store/cartSlice.js:

    Согласно материалам Хекслета, слайсы — это ключевой элемент RTK. > Слайсы не добавляют новых возможностей в сам Redux. Они автоматизируют рутину, сокращают количество кода и делают удобнее управление действиями и состоянием. > > ru.hexlet.io

    2. Настройка Хранилища (Store)

    Теперь нужно собрать все слайсы в единое хранилище. Создадим файл src/store/store.js:

    3. Подключение Store к React

    Чтобы React-компоненты «увидели» Redux, нужно обернуть приложение в компонент Provider из библиотеки react-redux. Вернемся в main.jsx:

    Использование Redux в компонентах

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

    Чтение данных: useSelector

    Хук useSelector принимает функцию-селектор, которая получает всё состояние и возвращает нужную его часть.

    Изменение данных: useDispatch

    Хук useDispatch возвращает функцию dispatch, с помощью которой мы отправляем действия (actions), созданные в слайсе.

    Архитектурный взгляд: когда использовать Redux?

    Не стоит переносить всё состояние в Redux.

  • Локальное состояние UI: Открыто ли выпадающее меню, текст в поле ввода формы — это лучше оставить в useState внутри компонента.
  • Глобальные данные: Данные пользователя, корзина, настройки темы, кэшированные ответы API — это идеальные кандидаты для Redux.
  • RTK Query: Взгляд в будущее

    В рамках Redux Toolkit также поставляется мощный инструмент RTK Query. Он предназначен специально для работы с серверными данными (кэширование, дедупликация запросов, автоматическое обновление). Мы подробно разберем его в следующих статьях, когда будем подключать реальный API.

    Согласно документации, RTK Query решает проблему написания однотипного кода для загрузки данных. > RTK Query — это инструмент для создания сервисов для запросов на сервер. Он позволяет создавать удобные интерфейсы, благодаря которым мы можем отслеживать состояния запросов и при этом не создавать много однообразного кода. > > ru.hexlet.io

    Итоги

    Мы превратили наше приложение из набора разрозненных компонентов в полноценную систему с навигацией и управлением данными.

  • React Router позволяет создавать SPA, где навигация происходит мгновенно без перезагрузки страницы. Используйте Link вместо <a> и useParams для динамических путей.
  • Redux Toolkit — это современный стандарт управления сложным состоянием. Он устраняет проблему Prop Drilling.
  • Slice (Слайс) — это модульная часть состояния, объединяющая логику (редьюсеры) и события (действия).
  • Поток данных: Компонент вызывает dispatch(action) -> Redux обновляет Store -> Компоненты перерисовываются благодаря useSelector.
  • Provider — обязательная обертка для приложения, связывающая React и Redux.