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-domはreactと同じバージョンをインストールしてください。バージョンの不一致は予期しないエラーの原因になります。
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.node の renderToPipeableStream を使います。
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-native | iOS / 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測定やスクロール制御など、本当に同期的な更新が必要な場面に限定して使いましょう。多くのケースでは useEffect や useLayoutEffect で代替できます。
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ブラウザで動かすために不可欠なパッケージです。クライアントサイドでは createRoot と hydrateRoot、サーバーサイドでは renderToPipeableStream や renderToReadableStream が中心的なAPIとなります。React 19 では旧来の ReactDOM.render が完全に削除され、リソースヒントAPIなどの新機能が追加されているため、移行時にはAPIの変更点を確認しておきましょう。