children の正体

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(要素)
  • 文字列、数値
  • それらの配列
  • nullundefined(何も表示されない)

ほとんどの場合、これを書いておけば足ります。 迷ったら 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>

titleaction は属性として、 中身は children として届きます。 差し込める場所を、枠のほうで好きに用意できるわけです。

複数の差し込み口を持つ枠

ヘッダーの右と、中身が別々

メンバー3 名

タイトルの隣にも、中身にも、別々の 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>
  );
}