Part 2 · コンポーネントと props
children の正体
タグで囲んだ中身が、なぜ props として届くのか
HTML では、タグで何かを囲むのが当たり前でした。
<div><p>中身</p></div>
自分で作ったコンポーネントでも、同じことができます。 そして囲んだ中身は、props として届きます。 その名前が children です。
囲んだ中身が props になる
コンポーネントをタグで囲むと、その中身が渡されます。
<Card>
<p>タイトル</p>
</Card>
// 届いているもの
{ children: <p>タイトル</p> }特別な仕組みではありません。children という名前の props があるだけです。 次の 2 つは、まったく同じ意味になります。
<Card children={<p>タイトル</p>} />
<Card>
<p>タイトル</p>
</Card>下の書き方ができるようになっているだけで、届き方は変わりません。
Card は中身を知らないまま、枠だけを提供している
タイトル
中身は呼ぶ側が自由に決められる
別の中身を入れても、同じ枠が使える
Card のコードを見ると、中に何が入るかをいっさい決めていません。 枠を用意して、受け取ったものをそこに置くだけです。
children-basic.tsx
// children という名前の props を受け取ると、
// タグで囲んだ中身がそこに入る
function Card({ children }: { children: React.ReactNode }) {
return <div className="rounded-md border p-3">{children}</div>;
}
export function ChildrenBasic() {
return (
<div className="flex flex-col gap-2.5">
<Card>
<p className="font-semibold">タイトル</p>
<p className="text-muted-foreground">中身は呼ぶ側が自由に決められる</p>
</Card>
<Card>
<p>別の中身を入れても、同じ枠が使える</p>
</Card>
</div>
);
}型は ReactNode
children の型には React.ReactNode を使います。 「画面に置けるもの」を表す型で、次のようなものが当てはまります。
- JSX(要素)
- 文字列、数値
- それらの配列
nullやundefined(何も表示されない)
ほとんどの場合、これを書いておけば足ります。 迷ったら ReactNode と覚えてしまって構いません。
children-basic.tsx
// children という名前の props を受け取ると、
// タグで囲んだ中身がそこに入る
function Card({ children }: { children: React.ReactNode }) {
return <div className="rounded-md border p-3">{children}</div>;
}
export function ChildrenBasic() {
return (
<div className="flex flex-col gap-2.5">
<Card>
<p className="font-semibold">タイトル</p>
<p className="text-muted-foreground">中身は呼ぶ側が自由に決められる</p>
</Card>
<Card>
<p>別の中身を入れても、同じ枠が使える</p>
</Card>
</div>
);
}差し込む場所は増やせる
children は「特別扱いされた名前」というだけで、 仕組みとしてはただの props です。 ということは、JSX を受け取る props はいくつでも作れます。
<Panel
title="メンバー"
action={<span>3 名</span>}
>
<p>中身</p>
</Panel>title と action は属性として、 中身は children として届きます。 差し込める場所を、枠のほうで好きに用意できるわけです。
ヘッダーの右と、中身が別々
タイトルの隣にも、中身にも、別々の JSX を差し込める
children-slots.tsx
import type { ReactNode } from "react";
// children 以外にも、JSX を受け取る props をいくつでも作れる
function Panel({
title,
action,
children,
}: {
title: ReactNode;
action: ReactNode;
children: ReactNode;
}) {
return (
<div className="rounded-md border">
<div className="flex items-center justify-between gap-3 border-b p-3">
<span className="font-semibold">{title}</span>
{action}
</div>
<div className="p-3">{children}</div>
</div>
);
}
export function ChildrenSlots() {
return (
<Panel
title="メンバー"
action={
<span className="rounded border px-2 py-0.5 text-xs text-muted-foreground">
3 名
</span>
}
>
<p className="text-muted-foreground">
タイトルの隣にも、中身にも、別々の JSX を差し込める
</p>
</Panel>
);
}この教材自身も使っている
いま読んでいるこのページも、同じ仕組みでできています。 解説を囲んでいる枠は、中身が何かを知りません。
<LessonShell snippets={snippets}>
{/* ここに書いた解説が children として届く */}
</LessonShell>この形は React のいたるところで出てきます。 レイアウト、モーダル、カード、リストの枠。「外側だけ用意して、中身は呼ぶ側が決める」が必要になったら、children の出番です。
children-slots.tsx
import type { ReactNode } from "react";
// children 以外にも、JSX を受け取る props をいくつでも作れる
function Panel({
title,
action,
children,
}: {
title: ReactNode;
action: ReactNode;
children: ReactNode;
}) {
return (
<div className="rounded-md border">
<div className="flex items-center justify-between gap-3 border-b p-3">
<span className="font-semibold">{title}</span>
{action}
</div>
<div className="p-3">{children}</div>
</div>
);
}
export function ChildrenSlots() {
return (
<Panel
title="メンバー"
action={
<span className="rounded border px-2 py-0.5 text-xs text-muted-foreground">
3 名
</span>
}
>
<p className="text-muted-foreground">
タイトルの隣にも、中身にも、別々の JSX を差し込める
</p>
</Panel>
);
}理解できたか確かめる
確認クイズ
children とは何?
確認クイズ
children を使う利点は?
children-basic.tsx
// children という名前の props を受け取ると、
// タグで囲んだ中身がそこに入る
function Card({ children }: { children: React.ReactNode }) {
return <div className="rounded-md border p-3">{children}</div>;
}
export function ChildrenBasic() {
return (
<div className="flex flex-col gap-2.5">
<Card>
<p className="font-semibold">タイトル</p>
<p className="text-muted-foreground">中身は呼ぶ側が自由に決められる</p>
</Card>
<Card>
<p>別の中身を入れても、同じ枠が使える</p>
</Card>
</div>
);
}この章のまとめ
- タグで囲んだ中身は、
childrenというprops として届く <Card children={...} />と書いても同じ。特別な仕組みではない- 型は
ReactNode。JSX・文字列・配列などを受け取れる - JSX を受け取る props はいくつでも作れる
- 利点は、枠を作る側が中身を知らなくて済むこと
children-basic.tsx
// children という名前の props を受け取ると、
// タグで囲んだ中身がそこに入る
function Card({ children }: { children: React.ReactNode }) {
return <div className="rounded-md border p-3">{children}</div>;
}
export function ChildrenBasic() {
return (
<div className="flex flex-col gap-2.5">
<Card>
<p className="font-semibold">タイトル</p>
<p className="text-muted-foreground">中身は呼ぶ側が自由に決められる</p>
</Card>
<Card>
<p>別の中身を入れても、同じ枠が使える</p>
</Card>
</div>
);
}