React Front-end Инженер

React Front-end Инженер

Роадмап навыков для прокачки

Зачем и как используют библиотеку ReactDOM

ReactСинтаксис JSXРендеринг

Основная идея

ReactDOM — это библиотека, которая отвечает за рендеринг React-компонентов в DOM браузера. Она является "мостом" между виртуальным деревом React и реальным DOM.

Ключевые аспекты

  • Разделение ответственности — React (логика компонентов) и ReactDOM (рендеринг в браузер) — разные пакеты
  • Точка входаReactDOM.createRoot() создаёт корневой контейнер для приложения
  • Метод render — связывает React-дерево с DOM-элементом на странице
  • ГидратацияhydrateRoot() используется для SSR (Server-Side Rendering)

Основные методы

  • createRoot(container) — создаёт корень приложения (React 18+)
  • root.render(element) — рендерит React-элемент в контейнер
  • hydrateRoot(container, element) — гидратация серверного HTML
  • flushSync(callback) — синхронный flush обновлений

Почему отдельная библиотека?

  • React может работать в разных средах (браузер, сервер, мобильные устройства)
  • ReactDOM — для браузера, React Native — для мобильных приложений
  • Уменьшает размер бандла — подключаете только нужный рендерер

Частые ошибки на собеседованиях

  • Путают React и ReactDOM — это разные пакеты с разными задачами
  • Используют устаревший ReactDOM.render() вместо createRoot() в React 18
  • Не знают про гидратацию при SSR — используют render вместо hydrateRoot

Введение и проблематика

React — это библиотека для создания пользовательских интерфейсов, но сам по себе React не знает, как отображать компоненты в браузере. Для этого существует ReactDOM — отдельный пакет, который отвечает за рендеринг React-компонентов в DOM браузера.

Разделение на React и ReactDOM произошло в версии 0.14 (2015), чтобы поддержать рендеринг в разных средах: браузер, сервер, мобильные устройства (React Native).

Зачем разделили React и ReactDOM?

  • React — содержит логику работы с компонентами, хуками, Virtual DOM
  • ReactDOM — умеет рендерить в браузерный DOM
  • React Native — умеет рендерить в нативные мобильные компоненты
  • React Three Fiber — рендеринг в WebGL/Three.js

Базовая теория

Что такое ReactDOM?

ReactDOM — это библиотека-рендерер, которая:

  1. Создаёт корневой контейнер для React-приложения
  2. Преобразует Virtual DOM в реальный DOM
  3. Эффективно обновляет DOM при изменениях состояния
  4. Обрабатывает события браузера

Основные методы ReactDOM

МетодОписаниеВерсия
createRoot()Создаёт корень для рендерингаReact 18+
hydrateRoot()Гидратация серверного HTMLReact 18+
flushSync()Синхронный flush обновленийReact 18+
render()Рендеринг (устарел)React 17 и ниже
hydrate()Гидратация (устарел)React 17 и ниже

Практические примеры

Базовое использование (React 18+)

Шаг 1: Установка пакетов

bash
npm install react react-dom

Шаг 2: Создание HTML-контейнера

html
<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Шаг 3: Инициализация React-приложения

Code Example 1: Что делает каждая из этих строк? Почему используется createRoot вместо старого ReactDOM.render()?

tsx
// main.tsx
import { createRoot } from 'react-dom/client';
import App from './App';
 
// Находим DOM-элемент для монтирования
const container = document.getElementById('root');
 
// Создаём корень приложения
const root = createRoot(container!);
 
// Рендерим главный компонент
root.render(<App />);

Сравнение старого и нового API

Code Example 2: В чём разница между этими двумя подходами? Какой из них предпочтительнее и почему?

tsx
// React 18+ — используем createRoot
import { createRoot } from 'react-dom/client';
import App from './App';
 
const container = document.getElementById('root')!;
const root = createRoot(container);
 
root.render(<App />);
 
// Для размонтирования:
// root.unmount();
⚠️

ReactDOM.render() устарел в React 18. Используйте createRoot() для включения всех новых возможностей React 18 (Concurrent Features, Automatic Batching).


Гидратация (Server-Side Rendering)

При использовании SSR сервер отправляет готовый HTML. На клиенте нужно "оживить" этот HTML, добавив обработчики событий и интерактивность. Этот процесс называется гидратацией.

Code Example 3: Что такое гидратация? Когда используется hydrateRoot вместо createRoot?

tsx
// Серверная часть (пример с Next.js или Express)
// Сервер генерирует HTML и отправляет клиенту
 
// Клиентская часть
import { hydrateRoot } from 'react-dom/client';
import App from './App';
 
const container = document.getElementById('root')!;
 
// Гидратация — НЕ перерисовывает, а "оживляет" существующий HTML
hydrateRoot(container, <App />);
🚫

Не используйте createRoot().render() для SSR! Это приведёт к полной перерисовке страницы и потере преимуществ серверного рендеринга.

Разница между render и hydrate

АспектcreateRoot().render()hydrateRoot()
ИспользованиеКлиентский рендеринг (CSR)Серверный рендеринг (SSR)
Что делаетСоздаёт DOM с нуляИспользует существующий HTML
ПроизводительностьМедленнее при первой загрузкеБыстрее — HTML уже готов
SEOХуже (пустой HTML)Лучше (контент в HTML)

Продвинутые возможности

flushSync — синхронное обновление

По умолчанию React батчит (группирует) обновления состояния для оптимизации. Иногда нужно принудительно применить изменения синхронно:

Code Example 4: Что делает flushSync? В каких ситуациях его использование оправдано?

tsx
import { flushSync } from 'react-dom';
import { useState } from 'react';
 
function App() {
  const [count, setCount] = useState(0);
 
  const handleClick = () => {
    flushSync(() => {
      setCount(c => c + 1);
    });
    // DOM уже обновлён здесь
    console.log(document.getElementById('counter')?.textContent);
  };
 
  return <div id="counter" onClick={handleClick}>{count}</div>;
}
⚠️

Используйте flushSync только когда это действительно необходимо. Чрезмерное использование ухудшает производительность.

Рендеринг в несколько контейнеров

React позволяет создавать несколько независимых корней на одной странице:

Code Example 5: Можно ли иметь несколько React-приложений на одной странице? Как они будут взаимодействовать?

tsx
import { createRoot } from 'react-dom/client';
 
// Виджет в хедере
const headerRoot = createRoot(document.getElementById('header-widget')!);
headerRoot.render(<HeaderWidget />);
 
// Основное приложение
const appRoot = createRoot(document.getElementById('app')!);
appRoot.render(<App />);
 
// Виджет в футере
const footerRoot = createRoot(document.getElementById('footer-widget')!);
footerRoot.render(<FooterWidget />);

Пограничные кейсы

⚠️

Типичная ошибка: забыть проверить существование контейнера.

Code Example 6: Какая проблема в Version A? Почему Version B безопаснее?

tsx
// ❌ Опасно — может быть null
const root = createRoot(document.getElementById('root'));
 
// ✅ Безопасно — явная проверка
const container = document.getElementById('root');
if (!container) {
  throw new Error('Root element not found');
}
const root = createRoot(container);

StrictMode для выявления проблем

Code Example 7: Для чего нужен StrictMode? Какие проблемы он помогает обнаружить?

tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
 
createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>
);

StrictMode помогает выявить:

  • Небезопасные lifecycle методы
  • Устаревшие API
  • Проблемы с побочными эффектами

Плюсы и минусы архитектуры

Плюсы разделения React и ReactDOM

  • Модульность — используйте только нужный рендерер
  • Меньший бандл — не тащите браузерный код в React Native
  • Гибкость — легко создавать кастомные рендереры
  • Тестируемость — можно тестировать логику без DOM

Минусы

  • Дополнительный пакет — нужно устанавливать оба пакета
  • Сложность для новичков — не сразу понятно разделение
  • Версионирование — нужно следить за совместимостью версий

Вопросы интервьюера

Q: Зачем нужен ReactDOM, если есть React?

React — это ядро с логикой компонентов. ReactDOM — рендерер для браузера. Разделение позволяет использовать React в разных средах.

Q: Чем отличается render от hydrate?

render создаёт DOM с нуля, hydrate "оживляет" существующий серверный HTML, добавляя интерактивность.

Q: Почему render устарел в React 18?

Новый API createRoot включает Concurrent Features и Automatic Batching. Старый render работает в legacy-режиме.

Q: Можно ли иметь несколько корней React на странице?

Да, можно вызвать createRoot несколько раз для разных контейнеров. Каждый корень независим.

Q: Что такое flushSync и когда его использовать?

flushSync принудительно синхронизирует DOM с состоянием. Используется редко, когда нужно немедленно прочитать обновлённый DOM.


Источники

Code Example 1: Basic React 18 initialization

❓ Что делает каждая из этих строк? Почему используется createRoot вместо старого ReactDOM.render()?

tsx
import { createRoot } from 'react-dom/client';
import App from './App';
 
const container = document.getElementById('root');
const root = createRoot(container!);
root.render(<App />);

Code Example 2: React 18 vs React 17 API

❓ В чём разница между этими двумя подходами? Какой из них предпочтительнее и почему?

React 18+:

tsx
import { createRoot } from 'react-dom/client';
import App from './App';
 
const container = document.getElementById('root')!;
const root = createRoot(container);
 
root.render(<App />);

React 17 и ниже:

tsx
import ReactDOM from 'react-dom';
import App from './App';
 
ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Code Example 3: Hydration for SSR

❓ Что такое гидратация? Когда используется hydrateRoot вместо createRoot?

tsx
import { hydrateRoot } from 'react-dom/client';
import App from './App';
 
const container = document.getElementById('root')!;
 
hydrateRoot(container, <App />);

Code Example 4: flushSync usage

❓ Что делает flushSync? В каких ситуациях его использование оправдано?

tsx
import { flushSync } from 'react-dom';
import { useState } from 'react';
 
function App() {
  const [count, setCount] = useState(0);
 
  const handleClick = () => {
    flushSync(() => {
      setCount(c => c + 1);
    });
    console.log(document.getElementById('counter')?.textContent);
  };
 
  return <div id="counter" onClick={handleClick}>{count}</div>;
}

Code Example 5: Multiple React roots

❓ Можно ли иметь несколько React-приложений на одной странице? Как они будут взаимодействовать?

tsx
import { createRoot } from 'react-dom/client';
 
const headerRoot = createRoot(document.getElementById('header-widget')!);
headerRoot.render(<HeaderWidget />);
 
const appRoot = createRoot(document.getElementById('app')!);
appRoot.render(<App />);
 
const footerRoot = createRoot(document.getElementById('footer-widget')!);
footerRoot.render(<FooterWidget />);

Code Example 6: Safe container check

❓ Какая проблема в Version A? Почему Version B безопаснее?

Version A:

tsx
const root = createRoot(document.getElementById('root'));

Version B:

tsx
const container = document.getElementById('root');
if (!container) {
  throw new Error('Root element not found');
}
const root = createRoot(container);

Code Example 7: StrictMode wrapper

❓ Для чего нужен StrictMode? Какие проблемы он помогает обнаружить?

tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
 
createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>
);