React — библиотека для пользовательских интерфейсов. Его главная идея: интерфейс описывается как функция от состояния приложения.
Вместо того чтобы вручную управлять DOM:
- искать элементы
- менять текст
- добавлять классы
- синхронизировать состояние
React предлагает описать, как интерфейс должен выглядеть при текущем состоянии. Всё остальное библиотека делает сама.
Упрощённая формула React:
UI = f(state)
Если состояние меняется — интерфейс автоматически перерисовывается.
Почему появился React
До React интерфейсы часто писались так:
const button = document.querySelector("#status")
if (user.online) {
button.textContent = "online"
button.classList.add("green")
} else {
button.textContent = "offline"
button.classList.add("gray")
}
Проблема такого подхода — логика интерфейса быстро превращается в хаос:
- состояние разбросано по коду
- DOM постоянно мутируется
- сложно поддерживать
React предлагает другой подход: описать интерфейс декларативно.
Декларативный UI
В React мы просто говорим: “Если пользователь онлайн — покажи зелёный статус. Если офлайн — серый.”
function StatusBadge({ online }) {
return online
? <span className="badge badge-green">online</span>
: <span className="badge badge-gray">offline</span>
}
Теперь React сам решает:
- когда обновлять DOM
- что изменилось
- какие элементы нужно перерисовать
Разработчик работает с состоянием, а не с DOM.
React — библиотека, а не фреймворк
React отвечает только за одну задачу: рендеринг интерфейса.
Он не диктует:
- структуру проекта
- роутинг
- работу с API
- управление состоянием
Поэтому вокруг React существует большая экосистема:
| задача | инструмент |
|---|---|
| сборка | Vite |
| роутинг | React Router |
| состояние | Zustand / Redux |
| запросы | TanStack Query |
| фреймворк | Next.js |
Это делает React очень гибким, но иногда требует больше решений от разработчика.
Где используется React
React используется для создания:
- SPA приложений
- админок
- SaaS сервисов
- интерфейсов стартапов
- сложных веб-приложений
Крупные продукты, использующие React:
- Airbnb
- Netflix
- Shopify
Также React стал основой многих фреймворков:
- Next.js
- Remix
- Expo (React Native)
Когда React — плохой выбор
React — мощный инструмент, но он не всегда нужен. Иногда лучше выбрать что-то проще:
Обычный сайт (лендинг, блог, маркетинговая страница)
Лучше использовать: HTML + CSS, Astro, статический генератор
Очень маленький интерфейс (простой виджет, небольшая форма)
React может быть избыточным.
Как работает React под капотом
Чтобы эффективно писать React-код, важно понимать три вещи:
- Virtual DOM
- Reconciliation
- Ререндеры компонентов
Virtual DOM
React не работает напрямую с браузерным DOM, потому что операции с ним дорогие.
Вместо этого React:
- создаёт Virtual DOM
- сравнивает его с предыдущей версией
- обновляет только изменившиеся части настоящего DOM
Представим компонент:
function CartTotal({ items }) {
const total = items.reduce((sum, item) => sum + item.price, 0)
return <h2>Total: ${total}</h2>
}
Если корзина меняется:
items = [
{ price: 10 },
{ price: 15 }
]
React рендерит: Total: $25
Когда добавляется новый товар:
items = [
{ price: 10 },
{ price: 15 },
{ price: 5 }
]
React делает следующее:
- создаёт новый Virtual DOM
- сравнивает его со старым
- находит отличие: 25 → 30
- обновляет только текст внутри <h2>
DOM-элемент при этом не пересоздаётся.
Reconciliation
Процесс сравнения Virtual DOM называется reconciliation.
React анализирует:
- какие элементы изменились
- какие можно переиспользовать
- какие нужно удалить или создать
Например:
<ul>
<li>Apple</li>
<li>Orange</li>
</ul>
После обновления:
<ul>
<li>Apple</li>
<li>Orange</li>
<li>Banana</li>
</ul>
React понимает: первые два элемента совпадают, добавился один новый. Поэтому он просто добавляет один <li>, а не перерисовывает весь список.
Почему важны ключи (key)
Когда React рендерит списки, он должен понимать: какой элемент соответствует какому.
Поэтому используется key.
function Notifications({ notifications }) {
return (
<ul>
{notifications.map(n => (
<li key={n.id}>
{n.message}
</li>
))}
</ul>
)
}
key помогает React:
- быстрее сравнивать списки
- избегать лишних ререндеров
- корректно обновлять элементы
Ререндер компонентов
Когда состояние меняется, React перерисовывает компонент.
Важно понять: рендер ≠ обновление DOM.
Рендер — это просто выполнение функции компонента.
Например:
function CartCounter({ items }) {
return <span>{items.length}</span>
}
Если состояние изменилось: items.push(newItem)
React:
- снова вызывает функцию CartCounter
- создаёт новый Virtual DOM
- сравнивает его со старым
- обновляет DOM только если есть изменения
Почему React быстрый
Несмотря на ререндеры, React остаётся быстрым благодаря:
- Virtual DOM — минимизирует реальные изменения DOM.
- Батчингу обновлений — React объединяет несколько обновлений в одно.
- Умному алгоритму diff — React анализирует структуру дерева и обновляет только изменившиеся узлы.
Создание проекта
Раньше React-проекты часто создавали с помощью Create React App, но сегодня стандартный способ — Vite.
Vite быстрее запускается, быстрее собирает проект и проще настраивается.
Создаём проект
В терминале выполните:
npm create vite@latest react-app
После этого перейдите в папку проекта:
cd react-app
npm install
npm run dev
После запуска Vite покажет адрес приложения:
http://localhost:5173
Откройте его в браузере — вы увидите стартовую страницу React.
Структура проекта
После создания проекта структура будет примерно такой:
react-app
├─ node_modules
├─ public
├─ src
│ ├─ App.jsx
│ ├─ main.jsx
│ ├─ index.css
│ └─ assets
├─ index.html
└─ package.json
Основная работа происходит в папке src.
Точка входа приложения
Файл:
src/main.jsx
Он подключает React к странице.
import React from "react"
import ReactDOM from "react-dom/client"
import App from "./App.jsx"
ReactDOM.createRoot(document.getElementById("root")).render(
<App />
)
Что здесь происходит:
- React находит элемент #root в HTML
- запускает приложение
- рендерит компонент App
Главный компонент
Файл:
src/App.jsx
Это основной компонент приложения.
Пример простого интерфейса:
function App() {
return (
<div>
<h1>My first React app</h1>
<p>React is running 🚀</p>
</div>
)
}
export default App
Когда вы сохраняете файл, Vite автоматически обновляет страницу.
Это называется Hot Reload.
Где писать код
Обычно проект постепенно разделяют на компоненты.
Например:
src
├─ components
│ ├─ Header.jsx
│ ├─ ProductCard.jsx
│ └─ UserMenu.jsx
├─ pages
│ ├─ Home.jsx
│ └─ Profile.jsx
└─ App.jsx
Так приложение становится легче поддерживать.
JSX — синтаксис React
Когда вы пишете React-компоненты, вы используете JSX.
JSX выглядит как HTML, но на самом деле это JavaScript.
Простой пример JSX
function App() {
return <h1>Hello React</h1>
}
На первый взгляд это HTML. Но JSX превращается в JavaScript.
Пример того, во что компилируется JSX:
React.createElement("h1", null, "Hello React")
Vite и Babel делают это автоматически.
JSX может использовать JavaScript
Внутри JSX можно писать JavaScript выражения.
Для этого используются фигурные скобки.
function Welcome() {
const name = "Anna"
return <h2>Hello {name}</h2>
}
Результат:
Hello Anna
Динамический интерфейс
JSX часто используется вместе с условиями.
function Temperature({ value }) {
return (
<div>
<p>Temperature: {value}°C</p>
{value < 0 && <p>It's freezing ❄️</p>}
</div>
)
}
Если температура ниже нуля, появляется дополнительный текст.
В JSX должен быть один родительский элемент
Это неправильно:
return (
<h1>Hello</h1>
<p>React</p>
)
Правильно:
return (
<div>
<h1>Hello</h1>
<p>React</p>
</div>
)
Или можно использовать Fragment:
return (
<>
<h1>Hello</h1>
<p>React</p>
</>
)
Fragment не создаёт дополнительный элемент в DOM.
Атрибуты в JSX
Атрибуты похожи на HTML, но иногда отличаются.
Например:
class → className
Пример:
function Button() {
return <button className="primary">Buy</button>
}
JSX можно разбивать на переменные
Это удобно, когда интерфейс становится сложнее.
function Product() {
const price = 29
const badge = price < 30
? <span>Cheap</span>
: <span>Premium</span>
return (
<div>
<h3>Product</h3>
{badge}
</div>
)
}
Компоненты в React
Главная идея React — интерфейс собирается из компонентов.
Компонент — это независимый кусок интерфейса, который можно переиспользовать.
Например, в интернет-магазине можно выделить компоненты:
- Header
- ProductCard
- Cart
- Button
- Footer
Каждый из них отвечает только за свою часть интерфейса.
Пример компонента:
function Button() {
return <button>Buy</button>
}
Теперь этот компонент можно использовать в других местах:
function App() {
return (
<div>
<Button />
<Button />
<Button />
</div>
)
}
React просто вызывает функцию Button каждый раз, когда встречает <Button />.
Переиспользование компонентов
Главное преимущество компонентов — повторное использование.
Вместо того чтобы копировать HTML, мы создаём компонент один раз.
Пример карточки товара:
function ProductCard() {
return (
<div className="product">
<h3>Phone</h3>
<p>$500</p>
<button>Buy</button>
</div>
)
}
Использование:
function App() {
return (
<div>
<ProductCard />
<ProductCard />
<ProductCard />
</div>
)
}
Но сейчас все карточки одинаковые.
Чтобы сделать их динамическими, используется props.
Props — параметры компонентов
Props — это данные, которые передаются в компонент.
Они работают как аргументы функции.
Пример:
function ProductCard(props) {
return (
<div className="product">
<h3>{props.title}</h3>
<p>${props.price}</p>
</div>
)
}
Использование:
function App() {
return (
<div>
<ProductCard title="Phone" price={500} />
<ProductCard title="Laptop" price={1200} />
<ProductCard title="Tablet" price={300} />
</div>
)
}
Теперь один компонент может отображать разные данные.
Деструктуризация props
Обычно props сразу распаковывают:
function ProductCard({ title, price }) {
return (
<div className="product">
<h3>{title}</h3>
<p>${price}</p>
</div>
)
}
Это делает код короче и удобнее.
Props только для чтения
Важно понимать:
Это только входные данные.
Неправильно:
function Counter({ value }) {
value = value + 1
}
Правильно: изменения должны происходить в родительском компоненте.
React придерживается принципа одностороннего потока данных.
Parent → Child → Child → Child
Данные всегда передаются сверху вниз.
Children — вложенные элементы
В React можно передавать не только данные, но и другие элементы интерфейса.
Для этого используется children.
Пример:
function Card({ children }) {
return (
<div className="card">
{children}
</div>
)
}
Использование:
function App() {
return (
<Card>
<h3>Product</h3>
<p>$50</p>
</Card>
)
}
React передаст всё, что находится между <Card> и </Card>, в children.
Состояние (State)
Props — это данные извне.
Но часто компоненту нужно хранить собственные данные.
Например:
- открыто ли модальное окно
- текст в поле ввода
- количество товаров в корзине
Для этого используется state.
В React состояние создаётся с помощью хука useState.
Пример:
import { useState } from "react"
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
)
}
Как работает useState
Строка:
const [count, setCount] = useState(0)
делает три вещи:
- создаёт состояние count
- задаёт начальное значение 0
- даёт функцию setCount для изменения состояния
Если вызвать:
setCount(count + 1)
React:
- обновит состояние
- заново вызовет компонент
- обновит интерфейс
Почему нельзя менять state напрямую
Нельзя делать так:
count = count + 1
React не узнает об изменении.
Правильный способ:
setCount(count + 1)
Ререндер при изменении state
Когда состояние меняется, React:
- снова вызывает компонент
- создаёт новый Virtual DOM
- сравнивает его со старым
- обновляет только изменившиеся части DOM
Пример:
function Counter() {
const [count, setCount] = useState(0)
console.log("render")
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
)
}
Каждый клик вызывает новый рендер.
Но DOM обновляется только там, где есть изменения.
Несколько состояний
Компонент может иметь несколько состояний.
function User() {
const [name, setName] = useState("Anna")
const [age, setAge] = useState(25)
return (
<div>
<p>{name}</p>
<p>{age}</p>
</div>
)
}
Каждое состояние работает независимо.
Обработка событий
React использует обработчики событий, похожие на обычный JavaScript.
Но они пишутся в camelCase.
HTML:
onclick
React:
onClick
Пример:
function Button() {
function handleClick() {
alert("Clicked")
}
return <button onClick={handleClick}>Click</button>
}
Можно использовать и стрелочную функцию:
<button onClick={() => console.log("clicked")}>
Click
</button>
Управляемые поля ввода
React часто управляет формами через состояние.
Пример:
function InputExample() {
const [text, setText] = useState("")
return (
<div>
<input
value={text}
onChange={e => setText(e.target.value)}
/>
<p>{text}</p>
</div>
)
}
Теперь значение поля всегда синхронизировано с состоянием.
Подъём состояния (Lifting State Up)
Иногда несколько компонентов должны использовать одно состояние.
В таком случае состояние поднимают в общего родителя.
Пример:
App
├─ Counter
└─ Counter
Состояние хранится в App и передаётся через props.
Почему React придумал Hooks
Когда React только появился, компоненты писались так:
class User extends React.Component {
state = { name: "Anna" }
componentDidMount() {
console.log("mounted")
}
render() {
return <h1>{this.state.name}</h1>
}
}
Такие компоненты назывались классовыми.
Но у них было несколько проблем:
- код быстро становился огромным
- логика разбрасывалась по разным методам
- сложно переиспользовать поведение
Например, логика загрузки данных могла быть в:
- componentDidMount
- componentDidUpdate
- componentWillUnmount
Код получался запутанным.
React решил проблему радикально.
Вместо классов — обычные функции.
Но функциям нужна была возможность:
- хранить состояние
- реагировать на изменения
- выполнять эффекты
Так появились Hooks.
Что такое Hook на самом деле
Hook — это просто специальная функция React.
Она даёт компоненту дополнительные возможности.
Например:
- useState → добавить состояние
- useEffect → выполнять действия после рендера
- useRef → хранить ссылку
Компонент при этом остаётся обычной функцией.
function Counter() {
// hooks
return <button>Click</button>
}
И это делает код намного проще.
useState — напоминание
Самый первый хук, который изучают — useState.
Он даёт компоненту память.
Без него компонент забывает всё после каждого рендера.
function Counter() {
let count = 0
return (
<button onClick={() => count++}>
{count}
</button>
)
}
Этот код не работает.
Почему?
Потому что после рендера React снова вызывает функцию, и count снова становится 0.
useState решает проблему.
const [count, setCount] = useState(0)
Теперь React запоминает значение между рендерами.
useEffect — когда нужно что-то сделать
Компоненты React должны быть чистыми функциями.
То есть они должны только возвращать интерфейс.
Но иногда нужно сделать что-то ещё.
Например:
- загрузить данные
- запустить таймер
- изменить title страницы
- подписаться на события
Такие действия называются: Side Effects.
Для них используется useEffect.
Представим реальную ситуацию
Допустим, мы хотим:
когда компонент появился → сделать запрос к API
С useEffect это выглядит так:
useEffect(() => {
console.log("component mounted")
}, [])
Пустой массив означает: выполнить эффект только один раз
То есть когда компонент появился на странице.
Пример из реального приложения
Представим страницу пользователей.
function Users() {
const [users, setUsers] = useState([])
useEffect(() => {
fetch("/api/users")
.then(r => r.json())
.then(data => setUsers(data))
}, [])
return (
<ul>
{users.map(u => (
<li key={u.id}>{u.name}</li>
))}
</ul>
)
}
Что происходит:
- React рендерит страницу
- useEffect делает запрос
- данные приходят
- состояние обновляется
- React делает новый рендер
И список появляется на странице.
Когда useEffect запускается
У useEffect есть три режима работы.
1 — каждый рендер
useEffect(() => {
console.log("render")
})
Этот эффект будет запускаться всегда.
Обычно так не делают.
2 — только один раз
useEffect(() => {
console.log("mounted")
}, [])
Это самый частый вариант.
3 — при изменении значения
useEffect(() => {
console.log("count changed")
}, [count])
Теперь эффект выполняется:
- при первом рендере
- и когда изменится count
Важный момент: порядок работы
React работает примерно так:
- рендер компонента
- обновление DOM
- запуск useEffect
То есть эффект выполняется после обновления интерфейса.
Это сделано специально, чтобы рендер был быстрым.
Очистка эффекта
Иногда эффект нужно остановить.
Например:
- таймер
- подписка
- WebSocket
- event listener
Для этого эффект может вернуть функцию.
useEffect(() => {
const id = setInterval(() => {
console.log("tick")
}, 1000)
return () => {
clearInterval(id)
}
}, [])
React вызовет эту функцию:
- перед удалением компонента
- или перед повторным запуском эффекта
Это предотвращает утечки памяти.
useRef — карман для хранения
Иногда нужно хранить значение, которое:
- не вызывает заново рендер
- но должно сохраняться
Для этого есть useRef.
const ref = useRef(0)
Теперь можно писать:
ref.current++
И значение сохранится между рендерами.
Но React не будет перерисовывать компонент.
useRef для доступа к DOM
Также useRef используют для доступа к DOM.
Например:
фокус на input
function Search() {
const inputRef = useRef(null)
function focusInput() {
inputRef.current.focus()
}
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>Фокус</button>
</div>
)
}
Hooks: главное, что нужно понять
Hooks нужны для того, чтобы функциональные компоненты могли:
- хранить состояние
- выполнять действия после рендера
- запоминать значения между рендерами
Самые важные хуки в начале:
- useState
- useEffect
- useRef
Именно их реально нужно понять в первую очередь.
08.03.2026
0
70
Комментарии
0