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 — умеет рендерить в нативные мобильные компоненты
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';constcontainer=document.getElementById('root')!;// Гидратация — НЕ перерисовывает, а "оживляет" существующий HTMLhydrateRoot(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';functionApp() {const [count,setCount] =useState(0);consthandleClick= () => {flushSync(() => {setCount(c => c +1); });// DOM уже обновлён здесьconsole.log(document.getElementById('counter')?.textContent); };return <divid="counter"onClick={handleClick}>{count}</div>;}
⚠️
Используйте flushSync только когда это действительно необходимо. Чрезмерное использование ухудшает производительность.
Рендеринг в несколько контейнеров
React позволяет создавать несколько независимых корней на одной странице:
❓
Code Example 5: Можно ли иметь несколько React-приложений на одной странице? Как они будут взаимодействовать?
tsx
import { createRoot } from'react-dom/client';// Виджет в хедереconstheaderRoot=createRoot(document.getElementById('header-widget')!);headerRoot.render(<HeaderWidget />);// Основное приложениеconstappRoot=createRoot(document.getElementById('app')!);appRoot.render(<App />);// Виджет в футереconstfooterRoot=createRoot(document.getElementById('footer-widget')!);footerRoot.render(<FooterWidget />);
Пограничные кейсы
⚠️
Типичная ошибка: забыть проверить существование контейнера.
❓
Code Example 6: Какая проблема в Version A? Почему Version B безопаснее?
tsx
// ❌ Опасно — может быть nullconstroot=createRoot(document.getElementById('root'));// ✅ Безопасно — явная проверкаconstcontainer=document.getElementById('root');if (!container) {thrownewError('Root element not found');}constroot=createRoot(container);
StrictMode для выявления проблем
❓
Code Example 7: Для чего нужен StrictMode? Какие проблемы он помогает обнаружить?