React — это JavaScript-библиотека для построения пользовательских интерфейсов из компонентов. Вы описываете, как должен выглядеть интерфейс при текущих данных, а React сам обновляет страницу, когда данные меняются. React отвечает только за слой интерфейса: маршрутизацию, загрузку данных и сборку проекта берут из других инструментов или фреймворков поверх React.
Содержание
Примеры в статье проверены 24.09.2026: React 19.3.0, Vite 8.3.0, шаблон create-vite 9.2.1, Node.js 22.23.1.
Коротко об истории и статусе
React создал в Facebook Джордан Уолк, код открыт в 2013 году (лицензия MIT). Хуки появились в React 16.8 (2019), React 19 вышел в декабре 2024 (19.3 — сентябрь 2026). Create React App с февраля 2025 года устарел: команда React советует фреймворк (Next.js, React Router), для обучения — Vite. С 2026 года React развивает React Foundation при Linux Foundation.
Классовые компоненты с render() и this.state, которые встречаются в старых учебниках, по-прежнему работают, но новый код пишут на функциях и хуках. Ниже — только современный вариант.
Пять терминов, которые путают
- Компонент — JavaScript-функция, которая возвращает разметку. Имя — с заглавной буквы:
TaskItem, а неtaskItem. - JSX — синтаксис, похожий на HTML, внутри JavaScript. Браузер его не понимает: сборщик превращает JSX в обычные вызовы функций.
- Элемент — объект-описание того, что нарисовать (результат JSX). Это еще не DOM-узел на странице.
- Props — входные данные компонента от родителя. Компонент их только читает.
- State (состояние) — данные, которые компонент хранит сам и меняет через функцию-сеттер. Смена state запускает повторный рендер.
Цепочка на одном примере: компонент App -> вызов функции App() возвращает дерево элементов -> React сравнивает его с прошлым деревом -> в DOM браузера попадают только изменения. Это упрощенная модель: на деле React разбивает работу на фазу рендера (вычислить новое дерево) и фазу фиксации (применить изменения к DOM), и может откладывать или объединять обновления.
Первое приложение на Vite
Vite 8 требует Node.js 20.19+ или 22.12+, но ветка 20 с 30.04.2026 не получает обновлений безопасности, поэтому ставьте актуальный LTS — Node.js 24 (или 22.12+). Команды в терминале:
node -v
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev
После npm run dev в терминале появится адрес вида http://localhost:5173/ — откройте его в браузере. Сгенерированный шаблон содержит index.html с пустым <div id="root"> и точку входа src/main.jsx:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
Здесь react-dom связывает React с DOM браузера, createRoot берет контейнер #root, а render рисует в нем компонент App. StrictMode — режим дополнительных проверок, он работает только при разработке.
Полный пример: список задач
Замените содержимое src/App.jsx целиком. В примере есть все основы сразу: два компонента, props, state, обработка событий, список с ключами и эффект.
import { useEffect, useState } from 'react'
function TaskItem({ task, onToggle }) {
return (
<li>
<label>
<input
type="checkbox"
checked={task.done}
onChange={() => onToggle(task.id)}
/>
{task.done ? <s>{task.title}</s> : task.title}
</label>
</li>
)
}
export default function App() {
const [tasks, setTasks] = useState([
{ id: 'a1', title: 'Установить Node.js', done: true },
{ id: 'a2', title: 'Создать проект на Vite', done: false },
])
const [title, setTitle] = useState('')
const left = tasks.filter((t) => !t.done).length
useEffect(() => {
document.title = `Осталось задач: ${left}`
}, [left])
function addTask(event) {
event.preventDefault()
const text = title.trim()
if (!text) return
setTasks((prev) => [
...prev,
{ id: crypto.randomUUID(), title: text, done: false },
])
setTitle('')
}
function toggle(id) {
setTasks((prev) =>
prev.map((t) => (t.id === id ? { ...t, done: !t.done } : t)),
)
}
return (
<main>
<h1>Мои задачи</h1>
<form onSubmit={addTask}>
<input
aria-label="Новая задача"
value={title}
onChange={(event) => setTitle(event.target.value)}
/>
<button type="submit">Добавить</button>
</form>
<ul>
{tasks.map((task) => (
<TaskItem key={task.id} task={task} onToggle={toggle} />
))}
</ul>
<p>Осталось: {left}</p>
</main>
)
}
Что увидите: заголовок «Мои задачи», поле с кнопкой, две задачи (первая зачеркнута) и строку «Осталось: 1». Вкладка браузера называется «Осталось задач: 1». Если ввести текст и нажать «Добавить», задача появится внизу списка; строка из одних пробелов не добавится. Клик по флажку зачеркивает задачу и пересчитывает счетчик.
Разбор по частям
Компоненты и props. TaskItem получает объект props и сразу раскладывает его на task и onToggle. Данные идут сверху вниз: App владеет списком, а TaskItem лишь сообщает наверх «переключи задачу с таким id» через переданную функцию. Это и называют однонаправленным потоком данных.
JSX. Фигурные скобки {} вставляют JavaScript-выражение: {left}, тернарный оператор, tasks.map(...). Строки внутри {} React экранирует, поэтому текст <b> из поля ввода покажется как текст, а не как тег. Отключить экранирование можно только явно через dangerouslySetInnerHTML — для пользовательского ввода так делать нельзя без очистки HTML.
Во что превращается JSX, видно через трансформацию Vite (transformWithOxc). Строка <h1 className="title">Привет, {name}</h1> становится вызовом:
/* @__PURE__ */ _reactJsxRuntime.jsxs("h1", {
className: "title",
children: ["Привет, ", name]
});
Атрибуты превращаются в свойства объекта, поэтому в JSX используют имена DOM-свойств: className вместо class (слово class зарезервировано в JavaScript) и htmlFor вместо for.
State. useState возвращает пару: текущее значение и функцию для его смены. Состояние не меняют напрямую (tasks.push(...) не вызовет перерисовку): создают новый массив или объект и передают его в сеттер. Поэтому в toggle используется map и копия { ...t, done: !t.done }.
Контролируемое поле. У input значение берется из state (value={title}), а каждое нажатие клавиши обновляет state через onChange. Так React остается единственным источником правды о содержимом поля, и его легко очистить через setTitle('').
Ключи списка. key={task.id} помогает React понять, какой элемент списка добавился, удалился или переместился. Ключ должен быть стабильным и уникальным среди соседей; индекс массива подходит только для списка, который никогда не переупорядочивают и не фильтруют.
Хуки: useState и useEffect
Хук — функция с именем на use, которая подключает к компоненту возможности React: состояние, эффекты, контекст. Два правила: вызывать хуки только на верхнем уровне компонента (не внутри if, циклов и вложенных функций) и только из компонентов или других хуков. React различает хуки по порядку вызова, поэтому условный вызов ломает соответствие.
useEffect синхронизирует компонент с внешним миром: заголовок вкладки, таймер, подписка, запрос к серверу. Второй аргумент — массив зависимостей: эффект из примера перезапускается только при смене left. Если эффект что-то запускает, он должен вернуть функцию очистки:
import { useEffect, useState } from 'react'
export function Clock() {
const [seconds, setSeconds] = useState(0)
useEffect(() => {
console.log('таймер запущен')
const id = setInterval(() => setSeconds((s) => s + 1), 1000)
return () => {
console.log('таймер остановлен')
clearInterval(id)
}
}, [])
return <p>Прошло секунд: {seconds}</p>
}
В режиме разработки с StrictMode консоль браузера после загрузки покажет:
таймер запущен
таймер остановлен
таймер запущен
Это не ошибка: в разработке React намеренно монтирует компонент, размонтирует и монтирует снова, чтобы проверить, что очистка написана правильно. В production-сборке (npm run build) эффект запустится один раз. Без clearInterval в очистке в разработке работали бы два таймера и секунды шли бы вдвое быстрее.
Типичная ошибка: state обновляется не сразу
Задача — по клику увеличить счетчик на 3.
import { useState } from 'react'
export function CounterBroken() {
const [count, setCount] = useState(0)
function addThree() {
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
}
return <button onClick={addThree}>Счет: {count}</button>
}
Результат после одного клика — «Счет: 1», а не 3. Внутри обработчика count — снимок значения на момент рендера (0), и все три вызова просят поставить 1. Исправление — функция-обновитель, которая получает актуальное значение из очереди обновлений:
function addThree() {
setCount((c) => c + 1)
setCount((c) => c + 1)
setCount((c) => c + 1)
}
После клика кнопка показывает «Счет: 3». Правило: если новое значение зависит от предыдущего, передавайте в сеттер функцию.
Если не получилось
| Симптом | Вероятная причина | Что сделать |
|---|---|---|
| Vite пишет, что нужна другая версия Node.js | Старый Node.js | Поставить Node.js 24 LTS (минимум 22.12+), проверить node -v |
| Белая страница | Ошибка в коде компонента | Открыть консоль браузера (F12) и прочитать первую красную ошибку |
Adjacent JSX elements must be wrapped in an enclosing tag. |
Компонент возвращает два элемента рядом | Обернуть в общий тег или во фрагмент <>...</> |
В консоли Each child in a list should have a unique "key" prop. |
У элементов из map нет key |
Добавить стабильный key, например id записи |
| Данные меняются, а экран нет | Изменен массив или объект напрямую | Создать копию и передать ее в сеттер |
| Эффект срабатывает бесконечно | Эффект меняет state, от которого сам зависит | Проверить массив зависимостей и условие внутри эффекта |
Дорожная карта до первого своего проекта: создать проект на Vite -> повторить пример со списком задач -> добавить удаление задачи -> сохранить список в localStorage через useEffect -> загрузить данные с API. Итоговый артефакт — небольшое приложение, которое можно показать в репозитории. Срок зависит от уровня JavaScript: без уверенного знания функций, массивов, объектов и модулей начинать лучше с них.
Выводы
- React — библиотека для интерфейсов: вы описываете вид интерфейса при текущих данных, React обновляет DOM сам.
- Интерфейс собирается из компонентов-функций; данные идут вниз через props, собственные данные компонента хранятся в state.
- JSX — синтаксис, который сборщик превращает в вызовы
jsx()/jsxs(), поэтому нужен Vite или фреймворк. useStateиuseEffectзакрывают большинство задач новичка; эффект с таймером или подпиской обязан возвращать очистку.- Если новое значение state зависит от старого, в сеттер передают функцию.
Где применяется / связь с практикой
Освойте тему на практике
React используют для личных кабинетов, админ-панелей, SaaS-интерфейсов и витрин, а через React Native — для мобильных приложений. Следующий шаг после основ — маршрутизация, работа с формами и API, управление состоянием между многими компонентами, тестирование и один из фреймворков поверх React. Системно пройти этот путь с проектами и ревью кода можно на курсе «React.js разработчик». Разобрать отдельные темы с преподавателями помогут бесплатные открытые уроки Otus.
FAQ
React — это библиотека или фреймворк?
Официально библиотека: она отвечает за интерфейс, а маршрутизацию, загрузку данных и серверный рендеринг добавляют фреймворки поверх React, например Next.js или React Router.
Нужен ли TypeScript, чтобы начать?
Нет, примеры работают на JavaScript. У Vite есть шаблон react-ts, и на TypeScript удобно переходить, когда проект вырастает.
Что такое виртуальный DOM и нужно ли о нем думать?
Так часто называют дерево элементов, которое React сравнивает с прошлым, чтобы менять в DOM только разницу. Новичку достаточно понимать, что на экран влияет только смена props и state.



