До появления современных UI-фреймворков веб-разработка опиралась на императивный подход (Imperative UI): разработчик вручную манипулировал DOM-деревом с помощью браузерных методов (document.getElementById, appendChild, classList.toggle). С ростом сложности приложений такой код неизбежно приводил к рассинхронизации состояния данных и визуального интерфейса.
React предложил фундаментальную смену парадигмы — декларативный пользовательский интерфейс (Declarative UI). Архитектурная формула React выражается математической функцией:
UI = f(State)
Разработчик описывает, как интерфейс должен выглядеть для любого заданного состояния, а фреймворк берет на себя вычисление минимального набора мутаций DOM для приведения реального дерева браузера в соответствие с целевым состоянием.
Синтаксическим фундаментом этой парадигмы является JSX (JavaScript XML) — статическое расширение синтаксиса JavaScript, позволяющее описывать иерархии компонентов в привычном XML-подобном виде. Важно понимать: браузеры не умеют исполнять JSX. JSX — это чистый синтаксический сахар, который компилятор (Babel, SWC или Turbopack) транспилирует в стандартные вызовы функций рантайма react/jsx-runtime.
Взглянем на то, чем на самом деле является JSX-элемент в рантайме React 19:
+-------------------------------------------------------------------------+
| МЕНТАЛЬНАЯ МОДЕЛЬ КОМПИЛЯЦИИ JSX |
+-------------------------------------------------------------------------+
| ИСХОДНЫЙ JSX-КОД (Пишет разработчик): |
| <button className="primary" onClick={handleClick}> |
| Сохранить |
| </button> |
+-------------------------------------------------------------------------+
| ТРАНСПИЛЯЦИЯ (SWC / Turbopack) |
| | |
| v |
+-------------------------------------------------------------------------+
| ВЫЗОВ РАНТАЙМА (react/jsx-runtime): |
| import { jsx as _jsx } from 'react/jsx-runtime'; |
| _jsx('button', { |
| className: 'primary', |
| onClick: handleClick, |
| children: 'Сохранить' |
| }); |
+-------------------------------------------------------------------------+
| РЕЗУЛЬТАТ В ПАМЯТИ: |
| Обычный легковесный JS-объект React Element ({ $$typeof, type, props })|
| (Это НЕ DOM-нода! Это лишь рецепт для Reconciler'а) |
+-------------------------------------------------------------------------+Компилятор (SWC/Turbopack) преобразует JSX-теги в вызовы функции _jsx(type, props, key).
Вызов функции компонента возвращает плоское дерево виртуальных дескрипторов в памяти.
Ядро React Fiber сравнивает новое дерево элементов с предыдущим и маркирует изменившиеся узлы.
Renderer (react-dom) точечно применяет мутации к реальному браузерному DOM (textContent, setAttribute).
Чем физически является результат выражения `const el = <h1>Привет</h1>` в оперативной памяти JavaScript-рантайма?
Header, Sidebar, ArticleContent).UserBadge для авторизованных пользователей vs кнопка Login).Card, ModalDialog, LayoutWrapper).Любое выражение JavaScript внутри JSX оборачивается в фигурные скобки {}:
interface UserProps {
name: string;
unreadCount: number;
avatarUrl?: string;
}
export function UserHeader({ name, unreadCount, avatarUrl }: UserProps) {
const isOnline = true;
return (
<header className="flex items-center justify-between p-4 border-b border-border">
{/* 1. Интерполяция строк и атрибутов */}
<div className="flex items-center gap-3">
<img
src={avatarUrl ?? '/default-avatar.png'}
alt={`Аватар пользователя ${name}`}
className="w-10 h-10 rounded-full"
/>
<span className="font-semibold text-text-primary">{name}</span>
</div>
{/* 2. Условный рендеринг через тернарный оператор */}
<div>
{unreadCount > 0 ? (
<span className="badge-notification">
Новых сообщений: {unreadCount}
</span>
) : (
<span className="text-text-muted text-sm">Все прочитано</span>
)}
</div>
{/* 3. Булев статус */}
{isOnline && <span className="w-2 h-2 rounded-full bg-emerald-500" />}
</header>
);
}React автоматически экранирует все значения, интерполируемые в фигурных скобках {}. Если злоумышленник внедрит строку <script>stealTokens()</script>, React преобразует ее в безопасный текст и не исполнит:
function CommentDisplay({ commentText }: { commentText: string }) {
// Допустим, commentText содержит: "<script>alert('hack')</script>"
// React НЕ вставляет это как сырой HTML. Он создает текстовый узел createTextNode().
return <p className="comment-body">{commentText}</p>;
}В современном React нет необходимости вручную писать import React from 'react' в начале каждого файла. Компилятор автоматически генерирует импорт функций jsx и jsxs:
// То, что пишет разработчик:
const element = <h2 className="title">WPSKILLS</h2>;
// Во что это компилирует SWC / Babel (автоматический рантайм React 19):
import { jsx as _jsx } from 'react/jsx-runtime';
const elementCompiled = _jsx('h2', {
className: 'title',
children: 'WPSKILLS',
});Что отрендерит в DOM выражение `const count = 0; return <div>{count && <span>Сообщений: {count}</span>}</div>`?
Если переменная длины массива равна 0, конструкция {items.length && <List items={items} />} выведет на экран одинокую цифру 0.
Решение: Всегда пишите явные булевы условия:
// ПЛОХО: на экране появится "0"
{items.length && <List items={items} />}
// ХОРОШО:
{items.length > 0 && <List items={items} />}Компонент React обязан возвращать единый корень (из-за того, что функция JavaScript может вернуть только одно значение).
// ОШИБКА:
// return <div>Раз</div><div>Два</div>;
// ПРАВИЛЬНО: используйте React.Fragment (<>)
return (
<>
<div>Раз</div>
<div>Два</div>
</>
);Рендер-функция компонента обязана быть чистой:
let renderCount = 0;
// АНТИПАТТЕРН: мутация глобальной переменной во время рендера!
function BadComponent() {
renderCount++; // Вызывает баги при Strict Mode и конкурентном рендере!
return <div>Рендер #{renderCount}</div>;
}