Синтаксис JSX — це візитна картка екосистеми React. З першого погляду здається, що це просто “HTML всередині JavaScript”, однак технічно — це набагато більше. JSX стирає межу між логікою (JS) та представленням (Markup), перетворюючи декларативний опис інтерфейсу на набір математичних викликів функцій. Під час цієї лекції ми заглянемо “під капот” JSX, розберемо, як саме він компілюється, які має апаратні та логічні обмеження, і яких стандартів безпеки дотримується.
Історично веб-фреймворки (Angular, Vue, Ember) та бекенд-системи (PHP Blade, Python Jinja) використовували шаблонізатори.
Шаблонізатори впроваджують “міні-мову” (з власними директивами типу ng-if, v-for) всередині звичайних HTML або стрінг-файлів. Головний недолік шаблонів — розробнику доводиться вивчати нову специфічну мову для кожного фреймворку, і ця мова дуже обмежена в порівнянні зі справжнім JavaScript.
React пішов іншим шляхом: він не став розширювати HTML, щоб дати йому можливість розуміти змінні. Він розширив сам стрінг JavaScript, щоб він розумів HTML-подібний синтаксис.
Цей підхід дозволяє використовувати всю потужність об’єктно-орієнтованого та функціонального програмування JS (наприклад, методи масивів .map(), .filter()) прямо під час побудови UI.
Браузери НЕ РОЗУМІЮТЬ JSX. Якщо ви відправите у браузер код із <div />, консоль видасть помилку синтаксису. Тому JSX вимагає етапу компіляції (трансляції).
Інструменти на кшталт Babel або SWC читають ваш код і будують AST (Абстрактне синтаксичне дерево). Побачивши JSX-вузол у дереві, вони замінюють його на виклик JS-функцій.
createElement до автоматичного режимуДо React 17 (Класичний режим):
Щоб компілятор міг перекласти <div /> у виклик React.createElement('div'), ми завжди мусили явно імпортувати React у кожен файл.
import React from "react"; // Обов'язково!
const el = <h1>Hello</h1>;
// Компілювалося в:
// const el = React.createElement('h1', null, 'Hello');
React 17+ (Новий JSX Трансформатор):
Компілятори суттєво змінились. Тепер вони автоматично ін’єктують (вставляють) спеціальні рантайм-функції. Імпорт React більше не потрібен (якщо ви не юзаєте Хуки).
const el = <h1>Hello</h1>;
// Компілюється в спеціальний оптимізований виклик:
import { jsx as _jsx } from "react/jsx-runtime";
const el = _jsx("h1", { children: "Hello" });
Функція return в JavaScript може повертати лише одне значення (об’єкт, рядок тощо). Оскільки кожен тег JSX перетворюється на виклик функції, компонент не може повернути два брати-теги без “батька” одночасно.
// ПОМИЛКА: Adjacent JSX elements must be wrapped in an enclosing tag.
function Profile() {
return (
<h2>John Doe</h2>
<p>Admin</p>
);
}
Щоб загорнути елементи без створення зайвого <div> в реальному HTML браузера, використовуються фрагменти: <React.Fragment> або їхній короткий запис <>.
function Profile() {
return (
<>
<h2>John Doe</h2>
<p>Admin</p>
</>
);
}
Примітка: Короткий запис <> не підтримує атрибути (наприклад key). Тому в списках (map) доводиться використовувати <React.Fragment key={id}>.
Як React відрізняє звичайний HTML-тег від вашого кастомного компонента? За великою літерою.
<header> (з малої літери) — перетворюється на рядок 'header', React будує тег для DOM.<Header /> (з великої) — React сприймає як виклик однойменної функції (вашого компоненту).Фігурні дужки {} в JSX дозволяють “відкрити вікно” назад у світ звичайного JavaScript. Але є жорстке правило: всередині дужок можна писати лише вирази (Expressions) (те, що повертає значення).
Писати інструкції (Statements) (наприклад if, for, switch) — заборонено.
// ПРАВИЛЬНО (Вирази):
<p>2 + 2 = {2 + 2}</p>
<p>{user.firstName.toUpperCase()}</p>
<div>{isLogged ? "Yes" : "No"}</div>
// НЕПРАВИЛЬНО (Викличе синтаксичну помилку):
// <div>{ if(true) { return "Yes" } }</div>
React ігнорує (не рендерить в екран) значення: false, null, undefined та true. Це дуже корисно для умовного рендерингу (про це в розділі 8).
Однак, нуль 0 — рендериться! Будьте обережні: { array.length && <List />} коли масив порожній, на екран виведеться 0. Правильно: { array.length > 0 && <List /> }.
JSX не є HTML, він набагато ближчий до JavaScript (оскільки він в ньому живе). Тому він дотримується JS-стандартів.
Оскільки class та for є зарезервованими словами в JavaScript, JSX використовує власний синтаксис для атрибутів:
class стає classNamefor стає htmlFortabindex стає tabIndexonclick стає onClick (і приймає функцію, а не рядок з кодом!)В HTML атрибут style приймає звичайний текст. В JSX — це об’єкт JavaScript з ключами в стилі camelCase.
// HTML
<div style="background-color: red; font-size: 14px;"></div>
// JSX (Дві пари дужок: зовнішні для 'відкриття' вікна в JS, внутрішні - це сам Об'єкт)
<div style=></div>
(Примітка: для кращої продуктивності у великих проектах інлайн-стилів зазвичай уникають на користь CSS Modules або Tailwind).
Кожного разу, коли ви рендерите дані через змінні (наприклад {userInput}), React конвертує все в рядок (String) ДО моменту вставки в об’єкт Virtual DOM. Це гарантує неможливість найбільш поширених XSS (Cross-Site Scripting) атак.
const badScript = "<script>alert('Hacked!');</script>";
// React виведе цей текст як ЗВИЧАЙНИЙ текст на екран, а скрипт НЕ виконається.
return <div>{badScript}</div>;
$$typeof та захист від підробки об’єктівВсі React об’єкти (елементи) мають вбудовану властивість $$typeof: Symbol.for('react.element'). Символи (Symbol) неможливо передати через JSON з бекенду.
Якщо хакер спробує передати готовий об’єкт (який схожий на React-вузол) з сервера в надії, що фронтенд відрендерить його і виконає шкідливий код, додаток просто крашнеться, тому що об’єкту з сервера не вистачатиме безпечного маркера $$typeof.
dangerouslySetInnerHTML та санітизаціяІноді вам ПОТРІБНО відрендерити “брудний” HTML (наприклад, з редактора тексту, як-от TinyMCE). У такому разі ви маєте на свій страх і ризик відключити захист React:
const articleBody = "<h1>Real HTML from DB</h1>";
// Розробник гарантує, що HTML безпечний (наприклад, пропущений через DOMPurify)
return <div dangerouslySetInnerHTML= />;
З приходом 19-ї версії React, розробка стала ще більш декларативною.
До версії 19, щоб змінити <title> сторінки із компоненту, потрібно було використовувати бібліотеку react-helmet. Тепер це працює нативно: якщо React бачить мета-теги всередині ваших компонентів, він автоматично піднімає їх у секцію <head> документа.
function BlogPost({ title }) {
return (
<article>
{/* Цей тег автоматично підніметься у <head> сторінки */}
<title>{title} - My Blog</title>
<h1>{title}</h1>
</article>
);
}
refРаніше, щоб прокинути ref-властивість всередину кастомного компонента, доводилося використовувати спеціальну обгортку forwardRef. У React 19 компонент може приймати ref як звичайний пропс поруч з іншими, значно спрощуючи побудову дизайн-систем.
// React 19
const CustomInput = ({ ref, placeholder }) => {
return <input ref={ref} placeholder={placeholder} />;
};
Оскільки ми не можемо використовувати if/else всередині JSX, розробники використовують 3 головні патерни:
Патерн Логічне І (&&) - коли потрібна умова “якщо так — малюємо, якщо ні — ігноруємо”:
{
unreadMessages.length > 0 && (
<span className="badge">You have new messages</span>
);
}
Тернарний оператор (? : ) - коли є дві гілки розвитку:
{
isLoading ? <Spinner /> : <Dashboard />;
}
Раннє повернення (Early Return) - винос імперативного if вище, за межі JSX:
function SecurePage({ user }) {
if (!user.isAuthed) {
return <LoginPrompt />; // JSX взагалі не дійде до коду нижче
}
return <SecretData />;
}
Оскільки JSX — це суміш, коментарі в ньому також пишуться по-особливому (як багаторядковий коментар JS всередині виразових дужок {}).
<div>
{/* Цей коментар не буде видно в інспекторі браузера (DOM) */}
<h1>Header</h1>
</div>
React.createElement до швидкого jsx-runtime у сучасних збирачах.<> для запобігання переповненню DOM “div-супом”.className, htmlFor та camelCase-події).$$typeof.class в HTML та className в JSX? Чому була запроваджена така зміна?if-else або циклу for) безпосередньо у фігурних дужках {} всередині JSX викликає краш компілятора?$$typeof у захисті архітектури?