react-dom の使い方

React package for working with the DOM.

v19.2.4114.1M/週MITフレームワーク
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

react-dom の使い方完全ガイド

一言でいうと

react-dom は、ReactコンポーネントをブラウザのDOM(またはサーバーサイドのHTML文字列)にレンダリングするための公式パッケージです。Reactの「仮想DOM → 実DOM」の橋渡し役を担います。

どんな時に使う?

  • Reactアプリケーションをブラウザで動かすとき — SPAのエントリーポイントでルートコンポーネントをDOMにマウントする
  • サーバーサイドレンダリング(SSR)を実装するときreact-dom/server を使ってReactコンポーネントをHTML文字列やストリームに変換する
  • 既存のDOM要素にReactコンポーネントを部分的に埋め込むとき — WordPressやRailsなど非SPAのページにReactウィジェットを追加する

インストール

# npm
npm install react react-dom

# yarn
yarn add react react-dom

# pnpm
pnpm add react react-dom

注意: react-domreact と同じバージョンをインストールしてください。バージョンの不一致は予期しないエラーの原因になります。

TypeScriptを使う場合は型定義もインストールします。

npm install -D @types/react @types/react-dom

基本的な使い方

React 19 では createRoot を使ってアプリケーションをマウントします。

// src/main.tsx
import { createRoot } from 'react-dom/client';
import { App } from './App';

const container = document.getElementById('root');

if (!container) {
  throw new Error('Root element not found');
}

const root = createRoot(container);
root.render(<App />);
// src/App.tsx
export function App() {
  return <h1>Hello, React 19!</h1>;
}

対応するHTMLファイル:

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>React App</title>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="/src/main.tsx"></script>
</body>
</html>

よく使うAPI

1. createRoot — クライアントサイドレンダリングの基本

import { createRoot } from 'react-dom/client';

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

// 初回レンダリング
root.render(<App />);

// 再レンダリング(同じrootに別のコンポーネントを描画可能)
root.render(<AnotherApp />);

// アンマウント
root.unmount();

createRoot にはオプションを渡すこともできます。

const root = createRoot(document.getElementById('root')!, {
  onCaughtError: (error) => {
    console.error('Error Boundary caught:', error);
  },
  onUncaughtError: (error) => {
    console.error('Uncaught error:', error);
  },
  onRecoverableError: (error) => {
    console.warn('Recoverable error:', error);
  },
});

2. hydrateRoot — SSR済みHTMLのハイドレーション

サーバーで生成したHTMLにイベントハンドラなどのインタラクティビティを付与します。

import { hydrateRoot } from 'react-dom/client';
import { App } from './App';

const container = document.getElementById('root')!;

// サーバーで生成されたHTMLを活かしつつReactを接続
const root = hydrateRoot(container, <App />);

// 必要に応じて再レンダリングも可能
root.render(<UpdatedApp />);

3. react-dom/server — サーバーサイドレンダリング

// server.ts
import { renderToString, renderToReadableStream } from 'react-dom/server';
import { App } from './App';

// 文字列として一括レンダリング
const html = renderToString(<App />);
console.log(html);
// => '<h1>Hello, React 19!</h1>'

ストリーミングSSR(Web Streams API対応環境):

import { renderToReadableStream } from 'react-dom/server';
import { App } from './App';

async function handler(req: Request): Promise<Response> {
  const stream = await renderToReadableStream(<App />, {
    bootstrapScripts: ['/src/main.js'],
  });

  return new Response(stream, {
    headers: { 'Content-Type': 'text/html' },
  });
}

Node.js環境では react-dom/server.noderenderToPipeableStream を使います。

import { renderToPipeableStream } from 'react-dom/server';
import { App } from './App';
import http from 'node:http';

http.createServer((req, res) => {
  const { pipe } = renderToPipeableStream(<App />, {
    bootstrapScripts: ['/src/main.js'],
    onShellReady() {
      res.setHeader('Content-Type', 'text/html');
      pipe(res);
    },
    onError(error) {
      console.error(error);
    },
  });
}).listen(3000);

4. flushSync — 同期的なDOM更新の強制

通常Reactの状態更新はバッチ処理されますが、flushSync を使うと即座にDOMへ反映できます。

import { flushSync } from 'react-dom';
import { useState } from 'react';

function ChatInput() {
  const [messages, setMessages] = useState<string[]>([]);
  const [input, setInput] = useState('');

  function handleSend() {
    // flushSync内の更新は即座にDOMに反映される
    flushSync(() => {
      setMessages((prev) => [...prev, input]);
      setInput('');
    });

    // この時点でDOMは更新済み → スクロール操作が正しく動く
    const list = document.getElementById('message-list');
    list?.scrollTo({ top: list.scrollHeight, behavior: 'smooth' });
  }

  return (
    <div>
      <ul id="message-list">
        {messages.map((msg, i) => (
          <li key={i}>{msg}</li>
        ))}
      </ul>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button onClick={handleSend}>送信</button>
    </div>
  );
}

5. createPortal — DOM階層外へのレンダリング

モーダルやツールチップなど、親コンポーネントのDOM階層外にレンダリングしたい場合に使います。

import { createPortal } from 'react-dom';
import { useState } from 'react';

function Modal({ children, onClose }: { children: React.ReactNode; onClose: () => void }) {
  return createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        {children}
        <button onClick={onClose}>閉じる</button>
      </div>
    </div>,
    document.body
  );
}

function App() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div>
      <button onClick={() => setShowModal(true)}>モーダルを開く</button>
      {showModal && (
        <Modal onClose={() => setShowModal(false)}>
          <h2>モーダルの中身</h2>
          <p>Portalによりbody直下にレンダリングされます。</p>
        </Modal>
      )}
    </div>
  );
}

類似パッケージとの比較

パッケージ対象プラットフォーム用途
react-domブラウザ / サーバー(HTML)Web向けReactレンダリング
react-nativeiOS / Androidモバイルアプリ向けReactレンダリング
react-three-fiberブラウザ(WebGL)Three.js向けReactレンダリング
inkターミナル(CLI)CLI向けReactレンダリング

react-dom はReactの「レンダラー」の一つです。React本体(reactパッケージ)はプラットフォーム非依存で、レンダラーを差し替えることで様々な環境に対応できるアーキテクチャになっています。

注意点・Tips

ReactDOM.render は React 19 で削除済み

React 18 で非推奨になった ReactDOM.render() は、React 19 で完全に削除されました。必ず createRoot を使ってください。

// ❌ React 19 では動かない
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));

// ✅ 正しい方法
import { createRoot } from 'react-dom/client';
createRoot(document.getElementById('root')!).render(<App />);

flushSync は最終手段

flushSync はバッチ処理を無効化するため、パフォーマンスに悪影響を与えます。DOM測定やスクロール制御など、本当に同期的な更新が必要な場面に限定して使いましょう。多くのケースでは useEffectuseLayoutEffect で代替できます。

SSRでの renderToString vs ストリーミング

renderToString はコンポーネントツリー全体をレンダリングし終えるまでレスポンスを返せません。大規模なページでは renderToPipeableStream(Node.js)や renderToReadableStream(Web Streams)を使ったストリーミングSSRを推奨します。Suspenseと組み合わせることで、準備できた部分から順次クライアントに送信できます。

ハイドレーションミスマッチに注意

SSRで生成したHTMLとクライアントの初回レンダリング結果が異なると、ハイドレーションエラーが発生します。よくある原因:

  • Date.now()Math.random() をレンダリング中に使う
  • サーバーとクライアントでロケールやタイムゾーンが異なる
  • typeof window !== 'undefined' による条件分岐をレンダリング中に行う

preload / preinit によるリソースヒント(React 19)

React 19 では react-dom からリソースのプリロードAPIが提供されています。

import { preload, preinit } from 'react-dom';

// フォントのプリロード
preload('/fonts/inter.woff2', { as: 'font', type: 'font/woff2', crossOrigin: 'anonymous' });

// CSSの事前初期化
preinit('/styles/global.css', { as: 'style' });

まとめ

react-dom はReactアプリケーションをWebブラウザで動かすために不可欠なパッケージです。クライアントサイドでは createRoothydrateRoot、サーバーサイドでは renderToPipeableStreamrenderToReadableStream が中心的なAPIとなります。React 19 では旧来の ReactDOM.render が完全に削除され、リソースヒントAPIなどの新機能が追加されているため、移行時にはAPIの変更点を確認しておきましょう。