React: что это такое и основы работы

React: что это такое и основы работы Полезное

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.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)