| title | createRoot |
|---|
createRoot は、ブラウザの DOM ノード内に React コンポーネントを表示するためのルートを作成します。
const root = createRoot(domNode, options?)createRoot を呼び出して、ブラウザの DOM 要素内にコンテンツを表示するための React ルートを作成します。
import { createRoot } from 'react-dom/client';
const domNode = document.getElementById('root');
const root = createRoot(domNode);React は domNode に対応するルートを作成し、その内部の DOM を管理します。ルートを作成した後、その内部に React コンポーネントを表示するために root.render を呼び出す必要があります。
root.render(<App />);React で完全に構築されたアプリには、ルートコンポーネントのための createRoot 呼び出しが通常 1 つのみ存在します。ページ内に React を「散りばめて」使用するページの場合は、必要なだけ複数のルートを持つことができます。
-
domNode: DOM 要素。React はこの DOM 要素に対応するルートを作成し、レンダーされた React コンテンツを表示するためのrenderなど、関数をルート上で呼び出せるようにします。 -
省略可能
options: この React ルートに関するオプションが含まれたオブジェクト。- 省略可能
onCaughtError: エラーバウンダリ内で React がエラーをキャッチしたときに呼び出されるコールバック。エラーバウンダリにキャッチされたerrorと、componentStackを含んだerrorInfoを引数にして呼び出されます。 - 省略可能
onUncaughtError: エラーがスローされたがエラーバウンダリでキャッチされなかったときに呼び出されるコールバック。スローされたerrorと、componentStackを含んだerrorInfoを引数にして呼び出されます。 - 省略可能
onRecoverableError: React が自動的にエラーから回復したときに呼び出されるコールバック。React がスローするerrorと、componentStackを含んだerrorInfoを引数にして呼び出されます。復帰可能なエラーの一部は元のエラーをerror.causeとして含んでいます。 - 省略可能
identifierPrefix: React がuseIdによって生成する ID に使用する文字列プレフィックス。同じページ上に複数のルートを使用する際に、競合を避けるために用います。
- 省略可能
createRoot は、render と unmount の 2 つのメソッドを持つオブジェクトを返します。
- アプリがサーバレンダリングを使用している場合、
createRoot()の使用はサポートされていません。代わりにhydrateRoot()を使用してください。 - アプリ内で
createRootを呼び出すのは通常 1 回だけです。フレームワークを使用している場合、この呼び出しはフレームワークが代わりに行う可能性があります。 - DOM ツリー内の、コンポーネントの子ではない別の部分に JSX をレンダーしたい場合(例えばモーダルやツールチップ)、
createRootの代わりにcreatePortalを使用してください。
root.render を呼び出して、React ルートのブラウザ DOM ノードに JSX("React ノード")を表示します。
root.render(<App />);React は root に <App /> を表示し、その内部の DOM の管理を行います。
reactNode: 表示したい React ノード。通常は<App />のような JSX ですが、createElement()で構築した React 要素や、文字列、数値、null、またはundefinedを渡すこともできます。
root.render は undefined を返します。
-
root.renderを初めて呼び出したとき、React は React ルート内の既存の HTML コンテンツをすべてクリアしてから、React コンポーネントをレンダーします。 -
ルートの DOM ノードがサーバやビルド中に React によって生成された HTML を含んでいる場合は、既存の HTML にイベントハンドラをアタッチできる
hydrateRoot()を使用してください。 -
同じルートに対して
renderを複数回呼び出すと、React は最新の JSX を反映するために必要なだけの DOM の更新を行います。React は、渡された JSX を以前にレンダーしたツリーと「マッチング」して、DOM のどの部分が再利用でき、どの部分を再作成する必要があるのかを決定します。同じルートに対して複数回renderを呼び出すことは、ルートコンポーネントでset関数を呼び出すことに似ており、React は不必要な DOM 更新を回避します。 -
レンダーは一旦始まると同期的に行われますが、
root.render(...)自体はそうではありません。つまりroot.render()の後のコードが、その特定のレンダー内のいずれかのエフェクト (useLayoutEffect,useEffect) よりも先に実行される可能性があるということです。これは通常問題なく、調整が必要になることは稀です。エフェクトのタイミングが重要となる稀なケースでは、初期レンダーが完全に同期的に実行されるように、root.render(...)をflushSyncで囲むことができます。const root = createRoot(document.getElementById('root')); root.render(<App />); // 🚩 The HTML will not include the rendered <App /> yet: console.log(document.body.innerHTML);
root.unmount を呼び出して、React ルート内にレンダーされたツリーを破棄します。
root.unmount();React で完全に構築されたアプリには、通常、root.unmount の呼び出しは一切ありません。
これは主に、React ルートの DOM ノード(またはその祖先のいずれか)が他のコードによって DOM から削除される可能性がある場合に有用です。例えば、非アクティブなタブを DOM から削除する jQuery のタブパネルがあると想像してみてください。タブが削除されると、(React ルートを含んだ)内部のすべてが DOM から削除されます。その場合、削除されたルートのコンテンツの管理を「停止」するよう React に伝えるために root.unmount を呼び出す必要があります。そうしないと、削除されたルート内のコンポーネントは、データ購読などのグローバルリソースをクリーンアップして解放する必要があるということが分からないままになります。
root.unmount を呼び出すことで、ルート内のすべてのコンポーネントがアンマウントされ、React がルート DOM ノードから「切り離され」ます。これには、ツリー内のイベントハンドラや state の削除も含まれます。
root.unmount は引数を受け付けません。
root.unmount は undefined を返します。
-
root.unmountを呼び出すと、ツリー内のすべてのコンポーネントがアンマウントされ、React がルート DOM ノードから「切り離され」ます。 -
root.unmountを呼び出した後、同一ルートで再度root.renderを呼び出すことはできません。アンマウント済みのルートでroot.renderを呼び出そうとすると、"Cannot update an unmounted root" というエラーがスローされます。ただし、ある DOM ノードに対する以前のルートがアンマウントされた後で、同じ DOM ノードに対して新しいルートを作成することは可能です。
アプリが完全に React で構築されている場合は、アプリ全体のための単一のルートを作成します。
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);通常、このコードは起動時に一度だけ実行する必要があります。このコードは以下のことを行います。
- HTML で定義されているブラウザの DOM ノードを見つけます。
- アプリの React コンポーネントをその内部に表示します。
<!DOCTYPE html>
<html>
<head><title>My app</title></head>
<body>
<!-- This is the DOM node -->
<div id="root"></div>
</body>
</html>import { createRoot } from 'react-dom/client';
import App from './App.js';
import './styles.css';
const root = createRoot(document.getElementById('root'));
root.render(<App />);import { useState } from 'react';
export default function App() {
return (
<>
<h1>Hello, world!</h1>
<Counter />
</>
);
}
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
You clicked me {count} times
</button>
);
}アプリが完全に React で構築されている場合、さらにルートを作成したり、root.render を再度呼び出したりする必要はありません。
この時点から、React がアプリ全体の DOM を管理します。さらにコンポーネントを追加するには、App コンポーネントの中にネストします。UI を更新する必要がある場合、各コンポーネントが state を使用して行います。モーダルやツールチップなどの追加コンテンツをこの DOM ノードの外部に表示する必要がある場合、ポータルを使ってレンダーします。
HTML が空の場合、アプリの JavaScript コードが読み込まれて実行されるまで、ユーザには空白のページが見え続けることになります。
<div id="root"></div>これは非常に遅く感じられることがあります! これを解決するために、サーバサイドで、あるいはビルド時に初期 HTML をコンポーネントから生成することができます。これにより、訪問者は JavaScript コードが読み込まれる前にテキストを読んだり、画像を見たり、リンクをクリックしたりすることができます。この最適化を自動で行うフレームワークの使用を推奨します。実行タイミングにより、この技術はサーバサイドレンダリング (server-side rendering; SSR) または 静的サイト生成 (static site generation; SSG) と呼ばれます。
サーバレンダリングまたは静的生成を使用するアプリは、createRoot の代わりに hydrateRoot を呼び出す必要があります。これにより React は、HTML に書かれた DOM ノードを破棄して再作成するのではなく、ハイドレーション(再利用)するようになります。
ページが完全には React で構築されていない場合、createRoot を複数回呼び出して、React に管理させたいトップレベルの各 UI パーツに対してルートを作成することができます。各ルートで root.render を呼び出して、それぞれに異なるコンテンツを表示することができます。
以下では、index.html ファイルに定義されている 2 つの異なる DOM ノードに 2 つの異なる React コンポーネントがレンダーされています。
<!DOCTYPE html>
<html>
<head><title>My app</title></head>
<body>
<nav id="navigation"></nav>
<main>
<p>This paragraph is not rendered by React (open index.html to verify).</p>
<section id="comments"></section>
</main>
</body>
</html>import './styles.css';
import { createRoot } from 'react-dom/client';
import { Comments, Navigation } from './Components.js';
const navDomNode = document.getElementById('navigation');
const navRoot = createRoot(navDomNode);
navRoot.render(<Navigation />);
const commentDomNode = document.getElementById('comments');
const commentRoot = createRoot(commentDomNode);
commentRoot.render(<Comments />);export function Navigation() {
return (
<ul>
<NavLink href="/">Home</NavLink>
<NavLink href="/about">About</NavLink>
</ul>
);
}
function NavLink({ href, children }) {
return (
<li>
<a href={href}>{children}</a>
</li>
);
}
export function Comments() {
return (
<>
<h2>Comments</h2>
<Comment text="Hello!" author="Sophie" />
<Comment text="How are you?" author="Sunil" />
</>
);
}
function Comment({ text, author }) {
return (
<p>{text} — <i>{author}</i></p>
);
}nav ul { padding: 0; margin: 0; }
nav ul li { display: inline-block; margin-right: 20px; }また、document.createElement() を使用して新しい DOM ノードを作成し、それを手動でドキュメントに追加することもできます。
const domNode = document.createElement('div');
const root = createRoot(domNode);
root.render(<Comment />);
document.body.appendChild(domNode); // You can add it anywhere in the documentDOM ノードから React ツリーを削除し、それによって使用されたすべてのリソースをクリーンアップするには、root.unmount を呼び出します。
root.unmount();これは主に、React コンポーネントが別のフレームワークで書かれたアプリの中にある場合に役立ちます。
同じルートに対して render を複数回呼び出すことができます。コンポーネントツリーの構造が以前にレンダーされたものと一致していれば、React は state を保持します。以下の例で入力フィールドにタイプできることに着目してください。つまり毎秒 render が繰り返し呼び出されていますが、更新により DOM が破壊されていないということです。
import { createRoot } from 'react-dom/client';
import './styles.css';
import App from './App.js';
const root = createRoot(document.getElementById('root'));
let i = 0;
setInterval(() => {
root.render(<App counter={i} />);
i++;
}, 1000);export default function App({counter}) {
return (
<>
<h1>Hello, world! {counter}</h1>
<input placeholder="Type something here" />
</>
);
}render を複数回呼び出すことは滅多にありません。通常、コンポーネントは代わりに state の更新を行います。
デフォルトでは、React はすべてのエラーをコンソールに記録します。独自のエラーレポートの仕組みを実装するには、省略可能なルートオプションとして onUncaughtError、onCaughtError、onRecoverableError のエラーハンドラを提供することができます。
import { createRoot } from "react-dom/client";
import { reportCaughtError } from "./reportError";
const container = document.getElementById("root");
const root = createRoot(container, {
onCaughtError: (error, errorInfo) => {
if (error.message !== "Known error") {
reportCaughtError({
error,
componentStack: errorInfo.componentStack,
});
}
},
});onCaughtError は以下の 2 つの引数で呼びされる関数です。
- スローされた error。
- errorInfo オブジェクト。エラーの componentStack を含んでいる。
onUncaughtError と onRecoverableError を組み合わせて、独自のエラーレポーティングのシステムを実装できます。
function reportError({ type, error, errorInfo }) {
// The specific implementation is up to you.
// `console.error()` is only used for demonstration purposes.
console.error(type, error, "Component Stack: ");
console.error("Component Stack: ", errorInfo.componentStack);
}
export function onCaughtErrorProd(error, errorInfo) {
if (error.message !== "Known error") {
reportError({ type: "Caught", error, errorInfo });
}
}
export function onUncaughtErrorProd(error, errorInfo) {
reportError({ type: "Uncaught", error, errorInfo });
}
export function onRecoverableErrorProd(error, errorInfo) {
reportError({ type: "Recoverable", error, errorInfo });
}import { createRoot } from "react-dom/client";
import App from "./App.js";
import {
onCaughtErrorProd,
onRecoverableErrorProd,
onUncaughtErrorProd,
} from "./reportError";
const container = document.getElementById("root");
const root = createRoot(container, {
// Keep in mind to remove these options in development to leverage
// React's default handlers or implement your own overlay for development.
// The handlers are only specfied unconditionally here for demonstration purposes.
onCaughtError: onCaughtErrorProd,
onRecoverableError: onRecoverableErrorProd,
onUncaughtError: onUncaughtErrorProd,
});
root.render(<App />);import { Component, useState } from "react";
function Boom() {
foo.bar = "baz";
}
class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
export default function App() {
const [triggerUncaughtError, settriggerUncaughtError] = useState(false);
const [triggerCaughtError, setTriggerCaughtError] = useState(false);
return (
<>
<button onClick={() => settriggerUncaughtError(true)}>
Trigger uncaught error
</button>
{triggerUncaughtError && <Boom />}
<button onClick={() => setTriggerCaughtError(true)}>
Trigger caught error
</button>
{triggerCaughtError && (
<ErrorBoundary>
<Boom />
</ErrorBoundary>
)}
</>
);
}アプリを実際にルートにレンダーするのを忘れていないか確認してください。
import { createRoot } from 'react-dom/client';
import App from './App.js';
const root = createRoot(document.getElementById('root'));
root.render(<App />);これを行うまでは何も表示されません。
"You passed a second argument to root.render" というエラーが出る {/im-getting-an-error-you-passed-a-second-argument-to-root-render/}
よくある間違いとして、createRoot 用のオプションを root.render(...) に渡してしまうことが挙げられます。
Warning: You passed a second argument to root.render(...) but it only accepts one argument.
修正するには、ルートオプションを createRoot(...) に渡すようにしてください。root.render(...) ではありません。
// 🚩 Wrong: root.render only takes one argument.
root.render(App, {onUncaughtError});
// ✅ Correct: pass options to createRoot.
const root = createRoot(container, {onUncaughtError});
root.render(<App />);"Target container is not a DOM element" というエラーが出る {/im-getting-an-error-target-container-is-not-a-dom-element/}
このエラーは、createRoot に渡しているものが DOM ノードでないことを意味します。
何が起こっているのかわからない場合は、ログを出力してみてください。
const domNode = document.getElementById('root');
console.log(domNode); // ???
const root = createRoot(domNode);
root.render(<App />);例えば、domNode が null の場合、getElementById が null を返したことを意味します。これは、呼び出し時点でドキュメント内に指定した ID のノードが存在しない場合に発生します。こうなる理由はいくつか考えられます。
- 探している ID が HTML ファイルで使用した ID と異なっている。タイプミスをチェックしてください!
- DOM ノードがバンドルの
<script>タグより後ろにあるため、スクリプトから「見え」ない。
このエラーが発生するもうひとつの一般的な理由は、createRoot(domNode) ではなく createRoot(<App />) と書いてしまっていることです。
"Functions are not valid as a React child." というエラーが出る {/im-getting-an-error-functions-are-not-valid-as-a-react-child/}
このエラーは、root.render に渡しているものが React コンポーネントでないことを意味します。
これは、root.render を <Component /> ではなく Component のように呼び出した場合に発生することがあります。
// 🚩 Wrong: App is a function, not a Component.
root.render(App);
// ✅ Correct: <App /> is a component.
root.render(<App />);または、root.render に関数を呼び出した結果ではなく関数自体を渡してしまった場合にも発生します。
// 🚩 Wrong: createApp is a function, not a component.
root.render(createApp);
// ✅ Correct: call createApp to return a component.
root.render(createApp());アプリがサーバでレンダーする形式であり、React によって生成された初期 HTML がある場合、ルートを作成して root.render を呼び出すと、その HTML がすべて削除されて DOM ノードがゼロから再作成されることに気付かれるかもしれません。これにより処理が遅くなり、フォーカスやスクロール位置がリセットされ、ユーザ入力が失われる可能性があります。
サーバでレンダーされたアプリは、createRoot の代わりに hydrateRoot を使用する必要があります。
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(
document.getElementById('root'),
<App />
);API が異なることに注意してください。特に、通常はこの後さらに root.render を呼び出すことはありません。