0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reactの children を理解する

Posted at

1. children とは何か?

children は、React コンポーネントに渡される特別な props のひとつです。
コンポーネントの 開始タグと終了タグのあいだに書かれた内容が、自動的に props.children として渡されます。

function Box({ children }) {
  return <div>{children}</div>;
}

<Box>
  <p>Hello</p>
</Box>

このとき、Reactは以下のように props を解釈します:

{
  children: <p>Hello</p>
}

つまり、React のコンポーネントを「箱」と見立てると、children はその中に詰められた中身です。

  • 親コンポーネントは「枠」や「構造」を定義
  • children はその中に自由に詰め込める「内容」

この仕組みによって、コンポーネントの中身を外部から差し替えたり、再利用したりすることが可能になります。

2.children の使い方パターン

パターン①:そのまま描画する(汎用ラッパー)

もっとも基本的な使い方。children を単純にそのまま描画する。

function Container({ children }) {
  return <div className="container">{children}</div>;
}
<Container>
  <p>Hello</p>
</Container>

向いている場面

  • カード、ボックス、レイアウト、モーダルなどの汎用 UI
  • コンテンツに制限を持たせず自由に使いたいとき

パターン②:children に処理を加える(map や wrap)

children に共通の加工(ラップ、装飾など)を施す。
複数の子を処理する際には React.Children.map を使用。

function List({ children }) {
  return (
    <ul>
      {React.Children.map(children, child => (
        <li>{child}</li>
      ))}
    </ul>
  );
}
<List>
  <span>Apple</span>
  <span>Banana</span>
</List>

向いている場面

  • リストアイテム、ナビゲーション、複数要素の装飾
  • コンテンツの個別レンダリングが必要な場面

パターン③:Function as Children(関数として子を渡す)

children を関数として渡し、親が状態を与えてレンダリングを制御。

function MouseTracker({ children }) {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const move = e => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener("mousemove", move);
    return () => window.removeEventListener("mousemove", move);
  }, []);
  return children(pos);
}
<MouseTracker>
  {pos => <p>Mouse at {pos.x}, {pos.y}</p>}
</MouseTracker>

向いている場面

  • 状態に応じたカスタム描画(例:グラフ、レスポンシブ対応)
  • ロジックとUIを分離したいケース

パターン④:Compound Component(構造的スロット)

子要素を名前付きコンポーネント(スロット)として構造的に分けるパターン。

function Card({ children }) {
  const header = React.Children.toArray(children).find(
    c => c.type === Card.Header
  );
  const body = React.Children.toArray(children).find(
    c => c.type === Card.Body
  );

  return (
    <div>
      <div className="card-header">{header}</div>
      <div className="card-body">{body}</div>
    </div>
  );
}

Card.Header = ({ children }) => <>{children}</>;
Card.Body = ({ children }) => <>{children}</>;
<Card>
  <Card.Header>タイトル</Card.Header>
  <Card.Body>本文</Card.Body>
</Card>

向いている場面

  • タブ、フォーム、モーダル、カードなどの複合 UI
  • 設計の明確さ・保守性を求めるケース

3. children の注意点と落とし穴

React公式ドキュメントでは次のように述べられています:

children の使用は一般的ではなく、コードが壊れやすくなる可能性があります」
“It’s uncommon to use children directly because it makes your code fragile.”
React.dev - Passing Props

これは「children 自体を使うな」という意味ではなく、
children の構造や順番に依存する設計をすると、脆くなるので注意せよという警告です。

落とし穴①:順番に依存した実装

function Layout({ children }) {
  const header = children[0]; // 1番目を header と決め打ち
  const content = children[1];
  return (
    <>
      <header>{header}</header>
      <main>{content}</main>
    </>
  );
}

なぜ危険?

  • 子要素が1つだけだと children は配列ではなくなる(構文エラーの原因)
  • 呼び出し側が順番を間違えると崩れる
  • 他の開発者が使う際に仕様がわかりにくい

落とし穴②:構造を前提にして children をハードコード

function Item({ children }) {
  return <li>{children}</li>;
}

<Item>
  <span>Apple</span>
  <span>Banana</span>
</Item>

なぜ危険?

  • <li> の中に複数の要素が入ると HTML 構造的に不正になる可能性がある
  • ブラウザによっては正しく解釈されず意図しない表示になる

落とし穴③:children の中身に型を仮定する

if (typeof children === 'string') {
  // 文字列として処理
}

なぜ危険?

  • children は JSX、配列、関数、null など様々な型を取りうる
  • このような分岐は意図しない挙動や壊れやすい設計につながる

4. 安全に扱うためには

React.Children.map() を使う

<ul>
  {React.Children.map(children, child => (
    <li>{child}</li>
  ))}
</ul>
  • 子要素が単一でも複数でも一貫して処理可能
  • key を指定することも可能
  • 配列かどうかを気にせず扱える

React.Children.toArray() を使って明示的に配列化

const childArray = React.Children.toArray(children);
childArray.map(child => <div>{child}</div>);
  • 配列としてループ処理でき、React が自動で key をつけてくれる
  • 順序操作・フィルタリングなどがしやすくなる

③ 明示的な props を使う(構造に依存しない設計)

function Layout({ header, content }) {
  return (
    <>
      <header>{header}</header>
      <main>{content}</main>
    </>
  );
}
<Layout
  header={<Header />}
  content={<Main />}
/>
  • children[0] のように構造に依存しない
  • 呼び出し側の意図も明確で安全

④ Compound Component パターンで構造を明示化

<Modal>
  <Modal.Title>タイトル</Modal.Title>
  <Modal.Content>本文</Modal.Content>
</Modal>
function Modal({ children }) {
  const title = React.Children.toArray(children).find(child => child.type === Modal.Title);
  const content = React.Children.toArray(children).find(child => child.type === Modal.Content);
  return (
    <>
      <h1>{title}</h1>
      <div>{content}</div>
    </>
  );
}
Modal.Title = ({ children }) => <>{children}</>;
Modal.Content = ({ children }) => <>{children}</>;
  • JSX構文を保ちながら、意味による構造分離ができる
  • 柔軟かつ堅牢な設計パターン

⑤ TypeScript で型の曖昧さを制御

type Props = {
  children: React.ReactNode;
};

または制約を加えるなら:

type Props = {
  children: React.ReactElement<MyComponentProps>[];
};
  • children の許容値を型で明示
  • 特定の要素のみ受け入れるように制限も可能

まとめ

  • children は React コンポーネントのタグの中に書かれた中身
  • 技術的には props.children として自動的に渡される「特別な props」
  • 概念的には、「中身を外部から差し込む柔軟な仕組み」
  • 子要素の 順番や数に依存する設計(例: children[0])は壊れやすい
  • <li>{children}</li> のように HTML構造に不適な使い方をしない
  • children の型を 仮定しない(stringとは限らない)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?