JSX (JavaScript XML) — это синтаксическое расширение JavaScript, позволяющее писать HTML-подобную разметку прямо в JS-коде. JSX — не HTML и не строка: это специальный синтаксис, который Babel компилирует в вызовы `React.createElement()`.
// JSX-код, который вы пишете:
const element = <h1 className="title">Привет, мир!</h1>
// После компиляции Babel превращает это в:
const element = React.createElement(
'h1', // тег
{ className: 'title' }, // атрибуты (props)
'Привет, мир!' // дочерний контент
)Понимание этой компиляции критически важно: JSX — просто удобный синтаксис для React.createElement().
React.createElement(
type, // строка ('div', 'h1') или компонент (функция/класс)
props, // объект атрибутов или null
...children // дочерние элементы (0 или более)
)Возвращает React-элемент — простой JS-объект, описывающий узел виртуального DOM:
// Что реально возвращает createElement:
{
type: 'h1',
props: {
className: 'title',
children: 'Привет, мир!'
},
key: null,
ref: null
}1. Один корневой элемент — JSX-выражение должно иметь один корень. Используйте <div> или пустой фрагмент <></>:
// Ошибка — два корневых элемента:
return <h1>Заголовок</h1><p>Абзац</p>
// Правильно — один корень:
return (
<>
<h1>Заголовок</h1>
<p>Абзац</p>
</>
)2. className вместо class — class — зарезервированное слово JS:
<div className="container">...</div>3. Самозакрывающиеся теги — все теги должны быть закрыты:
<img src="photo.jpg" alt="фото" />
<br />
<Input />4. Выражения в фигурных скобках — любое JS-выражение в {}:
const name = 'Алексей'
const element = <h1>Привет, {name}!</h1> // переменная
const el2 = <p>{2 + 2}</p> // выражение
const el3 = <p>{isAdmin ? 'Админ' : 'Гость'}</p> // тернарный оператор5. Атрибуты в camelCase — onclick → onClick, tabindex → tabIndex:
<button onClick={handleClick} tabIndex={0}>Кнопка</button>Вы уже знакомы с шаблонными строками из JavaScript и Vue-шаблонами. Разберём отличия:
| | Template literals | JSX |
|---|---|---|
| Синтаксис | \${expr}\ | {expr} |
| Результат | строка | React-объект |
| Тип | string | ReactElement |
| Безопасность | XSS уязвим | XSS защита |
| Обновление | innerHTML | Virtual DOM diffing |
JSX автоматически экранирует вставляемые значения, защищая от XSS-атак.
// JSX:
const card = (
<div className="card">
<h2>{title}</h2>
<p>{description}</p>
<button onClick={onClick}>Подробнее</button>
</div>
)
// Скомпилированный JS:
const card = React.createElement(
'div',
{ className: 'card' },
React.createElement('h2', null, title),
React.createElement('p', null, description),
React.createElement('button', { onClick: onClick }, 'Подробнее')
)Вложенность в JSX = вложенные вызовы createElement. Именно поэтому нужен один корневой элемент — это одно возвращаемое значение функции.
Реализация React.createElement и рендеринг вложенных элементов без JSX — понимаем что делает Babel
// Реализуем упрощённую версию React.createElement
// и рендеринг в DOM, чтобы понять что происходит под капотом JSX
// Шаг 1: createElement создаёт JavaScript-объект (виртуальный узел)
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
// children встраиваем в props (как делает React)
children: children.length === 1 ? children[0] : children
}
}
}
// Шаг 2: render превращает виртуальный узел в реальный DOM-элемент
function render(vnode) {
// Текстовые узлы — просто строки или числа
if (typeof vnode === 'string' || typeof vnode === 'number') {
return document.createTextNode(String(vnode))
}
const domElement = document.createElement(vnode.type)
// Применяем props как атрибуты DOM (кроме children)
const { children, ...attrs } = vnode.props || {}
for (const [key, value] of Object.entries(attrs)) {
if (key === 'className') {
domElement.className = value // JSX className -> DOM className
} else if (key.startsWith('on') && typeof value === 'function') {
const event = key.slice(2).toLowerCase() // onClick -> click
domElement.addEventListener(event, value)
} else {
domElement.setAttribute(key, value)
}
}
// Рекурсивно рендерим дочерние элементы
const childArray = Array.isArray(children) ? children : [children]
childArray.forEach(child => {
if (child != null) {
domElement.appendChild(render(child))
}
})
return domElement
}
// ============================================================
// Теперь используем наш createElement — это то, во что
// компилирует JSX Babel!
// ============================================================
const title = 'Карточка товара'
const price = 1990
const handleClick = () => console.log('Добавлено в корзину!')
// Это эквивалент JSX:
// <div className="card">
// <h2>{title}</h2>
// <p>Цена: {price} ₽</p>
// <button onClick={handleClick}>В корзину</button>
// </div>
const cardVNode = createElement('div', { className: 'card' },
createElement('h2', null, title),
createElement('p', null, `Цена: ${price} ₽`),
createElement('button', { onClick: handleClick }, 'В корзину')
)
console.log('Виртуальный DOM (объект):')
console.log(JSON.stringify(cardVNode, null, 2))
// Вывод:
// {
// "type": "div",
// "props": {
// "className": "card",
// "children": [
// { "type": "h2", "props": { "children": "Карточка товара" } },
// { "type": "p", "props": { "children": "Цена: 1990 ₽" } },
// { "type": "button", "props": { "children": "В корзину", ... } }
// ]
// }
// }
console.log('\nJSX компилируется именно в такую структуру объектов!')
console.log('React затем берёт эти объекты и строит реальный DOM.')JSX (JavaScript XML) — это синтаксическое расширение JavaScript, позволяющее писать HTML-подобную разметку прямо в JS-коде. JSX — не HTML и не строка: это специальный синтаксис, который Babel компилирует в вызовы `React.createElement()`.
// JSX-код, который вы пишете:
const element = <h1 className="title">Привет, мир!</h1>
// После компиляции Babel превращает это в:
const element = React.createElement(
'h1', // тег
{ className: 'title' }, // атрибуты (props)
'Привет, мир!' // дочерний контент
)Понимание этой компиляции критически важно: JSX — просто удобный синтаксис для React.createElement().
React.createElement(
type, // строка ('div', 'h1') или компонент (функция/класс)
props, // объект атрибутов или null
...children // дочерние элементы (0 или более)
)Возвращает React-элемент — простой JS-объект, описывающий узел виртуального DOM:
// Что реально возвращает createElement:
{
type: 'h1',
props: {
className: 'title',
children: 'Привет, мир!'
},
key: null,
ref: null
}1. Один корневой элемент — JSX-выражение должно иметь один корень. Используйте <div> или пустой фрагмент <></>:
// Ошибка — два корневых элемента:
return <h1>Заголовок</h1><p>Абзац</p>
// Правильно — один корень:
return (
<>
<h1>Заголовок</h1>
<p>Абзац</p>
</>
)2. className вместо class — class — зарезервированное слово JS:
<div className="container">...</div>3. Самозакрывающиеся теги — все теги должны быть закрыты:
<img src="photo.jpg" alt="фото" />
<br />
<Input />4. Выражения в фигурных скобках — любое JS-выражение в {}:
const name = 'Алексей'
const element = <h1>Привет, {name}!</h1> // переменная
const el2 = <p>{2 + 2}</p> // выражение
const el3 = <p>{isAdmin ? 'Админ' : 'Гость'}</p> // тернарный оператор5. Атрибуты в camelCase — onclick → onClick, tabindex → tabIndex:
<button onClick={handleClick} tabIndex={0}>Кнопка</button>Вы уже знакомы с шаблонными строками из JavaScript и Vue-шаблонами. Разберём отличия:
| | Template literals | JSX |
|---|---|---|
| Синтаксис | \${expr}\ | {expr} |
| Результат | строка | React-объект |
| Тип | string | ReactElement |
| Безопасность | XSS уязвим | XSS защита |
| Обновление | innerHTML | Virtual DOM diffing |
JSX автоматически экранирует вставляемые значения, защищая от XSS-атак.
// JSX:
const card = (
<div className="card">
<h2>{title}</h2>
<p>{description}</p>
<button onClick={onClick}>Подробнее</button>
</div>
)
// Скомпилированный JS:
const card = React.createElement(
'div',
{ className: 'card' },
React.createElement('h2', null, title),
React.createElement('p', null, description),
React.createElement('button', { onClick: onClick }, 'Подробнее')
)Вложенность в JSX = вложенные вызовы createElement. Именно поэтому нужен один корневой элемент — это одно возвращаемое значение функции.
Реализация React.createElement и рендеринг вложенных элементов без JSX — понимаем что делает Babel
// Реализуем упрощённую версию React.createElement
// и рендеринг в DOM, чтобы понять что происходит под капотом JSX
// Шаг 1: createElement создаёт JavaScript-объект (виртуальный узел)
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
// children встраиваем в props (как делает React)
children: children.length === 1 ? children[0] : children
}
}
}
// Шаг 2: render превращает виртуальный узел в реальный DOM-элемент
function render(vnode) {
// Текстовые узлы — просто строки или числа
if (typeof vnode === 'string' || typeof vnode === 'number') {
return document.createTextNode(String(vnode))
}
const domElement = document.createElement(vnode.type)
// Применяем props как атрибуты DOM (кроме children)
const { children, ...attrs } = vnode.props || {}
for (const [key, value] of Object.entries(attrs)) {
if (key === 'className') {
domElement.className = value // JSX className -> DOM className
} else if (key.startsWith('on') && typeof value === 'function') {
const event = key.slice(2).toLowerCase() // onClick -> click
domElement.addEventListener(event, value)
} else {
domElement.setAttribute(key, value)
}
}
// Рекурсивно рендерим дочерние элементы
const childArray = Array.isArray(children) ? children : [children]
childArray.forEach(child => {
if (child != null) {
domElement.appendChild(render(child))
}
})
return domElement
}
// ============================================================
// Теперь используем наш createElement — это то, во что
// компилирует JSX Babel!
// ============================================================
const title = 'Карточка товара'
const price = 1990
const handleClick = () => console.log('Добавлено в корзину!')
// Это эквивалент JSX:
// <div className="card">
// <h2>{title}</h2>
// <p>Цена: {price} ₽</p>
// <button onClick={handleClick}>В корзину</button>
// </div>
const cardVNode = createElement('div', { className: 'card' },
createElement('h2', null, title),
createElement('p', null, `Цена: ${price} ₽`),
createElement('button', { onClick: handleClick }, 'В корзину')
)
console.log('Виртуальный DOM (объект):')
console.log(JSON.stringify(cardVNode, null, 2))
// Вывод:
// {
// "type": "div",
// "props": {
// "className": "card",
// "children": [
// { "type": "h2", "props": { "children": "Карточка товара" } },
// { "type": "p", "props": { "children": "Цена: 1990 ₽" } },
// { "type": "button", "props": { "children": "В корзину", ... } }
// ]
// }
// }
console.log('\nJSX компилируется именно в такую структуру объектов!')
console.log('React затем берёт эти объекты и строит реальный DOM.')Создай компонент App, который демонстрирует ключевые правила JSX: один корневой элемент, выражения в фигурных скобках, атрибуты camelCase, самозакрывающиеся теги. Компонент должен отображать карточку с именем пользователя, его возрастом (вычисленным как 2026 - birthYear) и аватаром через тег img.
age = 2026 - birthYear = 31. Оберни всё в <div> или Fragment (<>...</>). Используй {name} и {age} для вставки переменных. Тег img — самозакрывающийся в JSX: <img src={...} alt={...} />.