← React/JSX: разметка в JavaScript#257 из 383← ПредыдущийСледующий →+15 XP
Полезно по теме:Гайд: React или VueПрактика: React setТермин: React HooksТема: React: хуки и экосистема

JSX: разметка в JavaScript

Что такое JSX

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

React.createElement(
  type,     // строка ('div', 'h1') или компонент (функция/класс)
  props,    // объект атрибутов или null
  ...children  // дочерние элементы (0 или более)
)

Возвращает React-элемент — простой JS-объект, описывающий узел виртуального DOM:

// Что реально возвращает createElement:
{
  type: 'h1',
  props: {
    className: 'title',
    children: 'Привет, мир!'
  },
  key: null,
  ref: null
}

Правила JSX

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>

JSX vs Template Literals

Вы уже знакомы с шаблонными строками из 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

Что такое JSX

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

React.createElement(
  type,     // строка ('div', 'h1') или компонент (функция/класс)
  props,    // объект атрибутов или null
  ...children  // дочерние элементы (0 или более)
)

Возвращает React-элемент — простой JS-объект, описывающий узел виртуального DOM:

// Что реально возвращает createElement:
{
  type: 'h1',
  props: {
    className: 'title',
    children: 'Привет, мир!'
  },
  key: null,
  ref: null
}

Правила JSX

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>

JSX vs Template Literals

Вы уже знакомы с шаблонными строками из 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={...} />.

Загружаем среду выполнения...
Загружаем AI-помощника...