Gernar
Frontend DeveloperReact

Интервью-вопрос

Что происходит с JSX при рендере

JSX сначала становится обычным JavaScript. При рендере React получает дерево React-элементов, сравнивает его с прошлым деревом и применяет к DOM только нужные изменения.

Добавлен
Редакция

Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.

🐰0
🥚0

Мини-квиз

Проверка перед разбором

Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.

Вопрос 1 из 60 правильно

Что лучше ответить про результат JSX?

Вы объясняете, что получится из JSX после компиляции и рендера.

Варианты ответа

Разбор

Разобраться, а не зазубрить

Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.

Базовая идея

JSX это удобный синтаксис для описания UI в React. Он похож на HTML, но не является HTML и не выполняется браузером напрямую. До выполнения код проходит через компилятор, например Babel, SWC или другой инструмент сборки.

На собеседовании удобно разделить ответ на два шага. Сначала JSX превращается в обычный JavaScript. Потом React во время рендера вызывает компоненты, получает React-элементы и решает, какие изменения нужны в интерфейсе.

Короткая формулировка для ответа:

JSX компилируется в JavaScript, который создает React-элементы. React-элементы это не DOM, а описание интерфейса. При обновлении React строит новое описание, сравнивает его с прошлым и применяет нужные изменения к DOM.

Что получается после компиляции

Исторически JSX часто показывают как синтаксический сахар над React.createElement.

const title = <h1 className="title">Hello</h1>;

Примерно так это можно объяснить через старую модель:

const title = React.createElement(
  "h1",
  { className: "title" },
  "Hello"
);

В современных проектах результат может выглядеть иначе, например через функции из react/jsx-runtime. Поэтому лучше не обещать один конкретный вызов для любого проекта. Главное сказать суть: JSX исчезает как синтаксис, а вместо него остается JavaScript, который создает React-элементы.

Если вы отвечаете коротко, можно сказать так: часто JSX объясняют через React.createElement, но в новом JSX transform могут быть jsx или jsxs из react/jsx-runtime. Суть не меняется. Создаются React-элементы.

Что такое React-элемент

React-элемент это объект с описанием UI. Он говорит React, какой тип нужно отобразить, какие props передать и какие children есть внутри. Для DOM-тега типом будет строка, например "div". Для компонента типом будет функция или объект компонента.

function UserCard({ name }) {
  return <section className="card">{name}</section>;
}

const element = <UserCard name="Ada" />;

В этом примере JSX не создает section сразу. Сначала создается элемент с типом UserCard и props { name: "Ada" }. Потом React вызовет компонент, получит JSX внутри него и продолжит строить дерево описаний.

Практический вывод простой: render должен быть чистым. Если вы делаете побочные эффекты прямо во время рендера, React может выполнить этот код повторно или не довести работу до commit phase. Для запросов, подписок и ручной работы с DOM нужны эффекты и cleanup, а не вычисление JSX.

Как React использует результат JSX

Как безопасно объяснить на собеседовании
  1. 1JSX компилируется в JavaScript-вызовы
  2. 2Компонент при рендере возвращает React-элементы
  3. 3React строит новое дерево и сопоставляет его с прошлым
  4. 4На commit phase React меняет только нужные DOM-узлы
Как лучше не отвечать
  1. 1Сказать, что браузер понимает JSX
  2. 2Назвать React-элемент реальным DOM-узлом
  3. 3Смешать render phase и изменение DOM
  4. 4Забыть про key, type и сохранение состояния

После изменения state или props React снова вызывает нужные компоненты и получает новое дерево React-элементов. Затем он сравнивает новое дерево с прошлым. Этот процесс обычно называют reconciliation.

React смотрит на типы элементов, порядок детей, ключи и props. Если тип элемента изменился, React может размонтировать старую часть дерева и создать новую. Если тип тот же, React может переиспользовать существующий DOM-узел или компонент и обновить только изменившиеся props.

DOM меняется не в момент, когда вы написали JSX, и не обязательно при каждом вызове компонента. Реальные изменения происходят на commit phase, когда React уже решил, что именно нужно применить.

Почему key связан с рендером JSX

key не попадает в props как обычное поле. Это специальная подсказка React для сопоставления элементов в списке. Она особенно важна, когда элементы можно добавлять, удалять, сортировать или фильтровать.

Плохой пример для динамического списка:

{users.map((user, index) => (
  <UserRow key={index} user={user} />
))}

Если список изменит порядок, React может связать старое состояние строки с другим пользователем. Так появляются странные баги: фокус остается не там, инпут показывает чужое значение, а анимация применяется к неправильной строке.

Безопаснее использовать стабильный идентификатор данных:

{users.map((user) => (
  <UserRow key={user.id} user={user} />
))}

На собеседовании достаточно сказать: key помогает React понять, какой ребенок в списке остался тем же самым между рендерами.

JSX, выражения и props

Внутри JSX фигурные скобки означают JavaScript-выражение. Это выражение вычисляется во время рендера компонента, а его результат становится частью props или children.

function Greeting({ user, isAdmin }) {
  return (
    <h1 className={isAdmin ? "admin" : "user"}>
      Hello, {user.name}
    </h1>
  );
}

Важно: внутри {} нужно выражение, а не любой оператор. Поэтому if напрямую как значение не подойдет. Обычно используют тернарный оператор, логическое &&, заранее подготовленную переменную или выносят ветвление выше.

Практический риск здесь не только синтаксический. Если вы вызываете тяжелую функцию прямо внутри JSX, она будет выполняться при каждом рендере компонента. Иногда это нормально. Но для дорогих вычислений лучше подумать о мемоизации, кэше или переносе вычисления ближе к данным.

Практические риски во время render

Выражения в JSX выполняются при рендере. Поэтому в render нельзя незаметно прятать сетевой запрос, подписку или запись во внешнее состояние. Такой код может выполниться много раз. В итоге появляются лишние запросы, гонки данных и обновление состояния после размонтирования.

Плохой пример:

function UserCard({ id }) {
  const [user, setUser] = React.useState(null);

  fetch(`/api/users/${id}`)
    .then((response) => response.json())
    .then(setUser);

  return user ? <Profile user={user} /> : <p>Загрузка...</p>;
}

Что сломается: каждый рендер запускает новый запрос, ответ старого запроса может перезаписать свежие данные, а после размонтирования компонент все еще может попытаться обновить state. Безопаснее вынести запрос в эффект, обработать loading и error, а старый запрос отменять.

function UserCard({ id }) {
  const [user, setUser] = React.useState(null);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    const controller = new AbortController();

    setUser(null);
    setError(null);

    fetch(`/api/users/${id}`, { signal: controller.signal })
      .then((response) => {
        if (!response.ok) throw new Error("Request failed");
        return response.json();
      })
      .then(setUser)
      .catch((error) => {
        if (error.name !== "AbortError") setError(error);
      });

    return () => controller.abort();
  }, [id]);

  if (error) return <p role="alert">Не удалось загрузить профиль</p>;
  if (!user) return <p>Загрузка...</p>;

  return <Profile user={user} />;
}

JSX и внешние данные

Обычный вывод строки в JSX React экранирует. Если user.name содержит HTML, запись {user.name} покажет текст, а не выполнит тег или скрипт. Это помогает против XSS при обычном выводе данных.

Риск появляется, когда вы обходите эту защиту через dangerouslySetInnerHTML или ручной innerHTML. Тогда HTML из CMS, комментария или API нужно санитизировать до рендера и использовать только если без этого нельзя. На собеседовании это хороший практический вывод: JSX не равен HTML-строке, а безопасность зависит от способа вставки данных.

Что важно для Next.js

В Next.js JSX тоже компилируется. Разница в том, где выполняется часть рендера. Серверные компоненты могут быть отрендерены на сервере, а клиентские компоненты затем гидрируются в браузере. При SSR сервер может отдать HTML, но это не значит, что JSX сам стал HTML в браузере.

Если вас спрашивают про Next.js, ответ можно собрать так: JSX проходит компиляцию, затем React может использовать его результат на сервере для генерации HTML или на клиенте для интерактивного UI. На клиенте React сопоставляет разметку с деревом компонентов во время hydration. Если результат рендера на сервере и клиенте отличается, можно получить hydration mismatch.

Практический вывод

Хороший ответ на этот вопрос не должен застревать на фразе "JSX это синтаксический сахар". Добавьте, что именно создается, когда это происходит и как React потом применяет изменения.

Рабочая структура ответа:

  1. JSX компилируется в JavaScript до выполнения.
  2. JavaScript-вызовы создают React-элементы, а не DOM.
  3. Компоненты при рендере возвращают дерево React-элементов.
  4. React сравнивает новое дерево с прошлым и применяет изменения к DOM на commit phase.
  5. key, type и props влияют на то, что будет переиспользовано, а что будет пересоздано.

Такой ответ показывает, что вы понимаете и сборку, и runtime-поведение React. Это звучит сильнее, чем просто пересказать пример с React.createElement.

Частые ошибки

Где обычно ошибаются

Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.

  1. 1

    Называть JSX HTML

    JSX похож на HTML, но это синтаксис из JavaScript-экосистемы. У него есть отличия вроде className, htmlFor, JavaScript-выражений в фигурных скобках и объектов для style. Если назвать JSX обычным HTML, следующим вопросом у вас могут уточнить props или выражения.
  2. 2

    Думать, что createElement создает DOM

    React.createElement или функции JSX runtime создают React-элемент. Это описание будущего UI. DOM создается или обновляется позже, во время commit phase. Так вы не смешаете вычисление интерфейса с побочными эффектами в браузере.
  3. 3

    Говорить только про React.createElement

    В старых материалах это частый ответ, но в современных проектах компилятор может использовать react/jsx-runtime. Лучше сказать шире: JSX компилируется в JavaScript-вызовы, которые создают React-элементы. После этого можно добавить, что исторически это часто объясняют через React.createElement.
  4. 4

    Смешивать render и commit

    Во время render phase React вычисляет новое дерево. Эту работу можно прервать или повторить. Реальные изменения DOM происходят на commit phase. Если забыть это различие, легко ошибиться в вопросах про эффекты, Strict Mode и производительность.
  5. 5

    Игнорировать key в списках

    key нужен не для красоты и не только для warning. Он помогает React понять, какой элемент остался тем же самым после изменения массива. Нестабильный key может перенести состояние инпута или компонента на другой элемент списка.
  6. 6

    Делать побочные эффекты во время render

    Если вызвать fetch, подписку или запись в localStorage прямо при вычислении JSX, код может выполниться при каждом рендере. Это приводит к лишним запросам, гонкам данных и обновлению состояния после размонтирования. Запросы и подписки выносите в useEffect или обработчики. Для эффектов добавляйте cleanup и отмену запроса.
  7. 7

    Думать, что JSX безопасно вставляет любой HTML

    Обычный вывод строк в JSX экранируется, поэтому {user.name} не станет HTML. Но dangerouslySetInnerHTML обходит эту защиту. Если вы рендерите внешний HTML, нужна санитизация и понятная причина. Иначе легко получить XSS.

Follow-up

Что могут спросить дальше

Короткие ответы на вопросы, которыми проверяют понимание JSX, React-элементов и рендера.

Живые ответы

Видео с похожим вопросом

Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.

Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.

Содержание