React でコンポーネントを作っていると、「見た目の枠は共通で、中身だけ差し替えたい」という場面が必ず出てきます。カード、モーダル、共通レイアウトなどがその代表です。これを実現するのが children という特別な prop です。この記事では、コンポーネントのタグで囲んだ中身がどうやって渡るのか、children を受け取る書き方、TypeScript での型の付け方、そして応用的な使い方までを、初心者にもわかりやすく解説します。props の基本を押さえたうえで読むと、より理解が深まります。
目次
children とは「タグで囲んだ中身」のこと
ふだん HTML では、<div>ここに中身</div> のようにタグで内容を囲みます。React の自作コンポーネントでも同じことができ、開始タグと終了タグで囲んだ中身は、そのコンポーネントに children という名前の prop として自動的に渡されます。
// children を受け取って、枠の中に表示する
function Card({ children }: { children: React.ReactNode }) {
return <div className="card">{children}</div>;
}
// 使う側:タグで囲んだ中身が children として渡る
function App() {
return (
<Card>
<h2>タイトル</h2>
<p>これはカードの本文です。</p>
</Card>
);
}
<Card> と </Card> で囲んだ <h2> と <p> が、Card の中の {children} の位置にそのまま表示されます。Card は「枠」だけを提供し、中身は使う側が自由に決められる——これが children の基本的な考え方です。
なぜ children を使うのか
「中身も props で渡せばいいのでは?」と思うかもしれません。たしかに文字列1つなら props で十分です。しかし、渡したいのが見出し+本文+ボタンのような複数の要素のまとまりだったり、さらにその中に別のコンポーネントが入っていたりする場合、それを全部 props で受け渡すのは大変です。children なら、HTML を書く感覚でそのまま囲むだけで渡せます。
この仕組みのおかげで、同じ枠を色々な中身で使い回せます。たとえば Card を1つ作れば、お知らせカード、プロフィールカード、商品カードと、中身を変えるだけで何にでも使えます。「見た目の共通部分」と「中身」を分離できるのが、children の大きな利点です。
// 同じ Card を、中身を変えて何度でも使える
function App() {
return (
<>
<Card>
<p>1つ目のカードです。</p>
</Card>
<Card>
<h3>2つ目</h3>
<button>ボタン</button>
</Card>
</>
);
}
TypeScript での型の付け方
TypeScript で children を受け取るときは、その型を指定する必要があります。children には文字列、数値、JSX 要素、要素の配列など、画面に表示できるものなら何でも入るため、それらをまとめて表す React.ReactNode を使うのが基本です。
// props の型をまとめて定義する書き方
type CardProps = {
children: React.ReactNode;
};
function Card({ children }: CardProps) {
return <div className="card">{children}</div>;
}
React.ReactNode は、React で「表示できるもの」全般を表す型です。JSX 要素はもちろん、文字列や数値、null(何も表示しない)、それらの配列まで含みます。children の型に迷ったら、まず React.ReactNode を選んでおけば間違いありません。
children と他の props を組み合わせる
children は特別な名前というだけで、他の props と一緒に使えます。たとえば「タイトルは props で受け取り、本文は children で受け取る」といった設計にすると、枠の一部は固定しつつ、中身は柔軟に、というコンポーネントが作れます。
type PanelProps = {
title: string; // 見出しは props で
children: React.ReactNode; // 中身は children で
};
function Panel({ title, children }: PanelProps) {
return (
<section className="panel">
<h2>{title}</h2>
<div className="panel-body">{children}</div>
</section>
);
}
// 使う側
function App() {
return (
<Panel title="お知らせ">
<p>本日のメンテナンスは完了しました。</p>
</Panel>
);
}
このように、title は通常の prop として属性で渡し、本文はタグの中身(children)として渡します。役割ごとに渡し方を分けることで、使う側にとって分かりやすいコンポーネントになります。
中身が渡されないときに備える
コンポーネントを空のタグで使うと、children は空になります。中身が無いときに代わりの表示を出したい場合は、children があるかどうかで分岐します。JSX では、値が入っていれば表示し、なければ別の内容を出す、といった書き方ができます。
function Box({ children }: { children?: React.ReactNode }) {
return (
<div className="box">
{/* children があればそれを、なければ代わりの文言を表示 */}
{children ?? <p>内容がありません</p>}
</div>
);
}
中身が省略される可能性があるときは、型を children?: React.ReactNode のように ? を付けて「あってもなくてもよい」と表しておくと、使う側で空にしても型エラーになりません。
まとめ
children は、コンポーネントのタグで囲んだ中身を受け取る特別な prop です。これを使うと、カードやモーダル、共通レイアウトのように「枠は共通・中身は自由」というコンポーネントを簡単に作れ、同じ枠を色々な中身で再利用できます。TypeScript では型に React.ReactNode を使うのが基本で、省略される可能性があるなら ? を付けます。title などの他の props と組み合わせれば、固定したい部分と差し替えたい部分を分けた、使いやすいコンポーネントになります。「見た目の共通化」を考え始めたら、まず children で中身を差し込めないかを検討してみましょう。