React

【React】propsのchildrenの基本的な使い方と実例(再利用性の高い部品作成方法)

Reactのpropsのchildrenの使い方を解説します。タグの間に書いた要素がchildrenとして渡る仕組み、受け取り方、順番の制御、React.Children、TypeScriptの型、再利用可能なCardコンポーネントの実例とよくあるつまずきを紹介します。

この記事の目次
  1. childrenの基本的な使い方
  2. 他のコンポーネントをラップするコンポーネント(親)
  3. childrenとしてラップされる側のコンポーネント(子)
  4. 渡すコンポーネントの順番を指定する方法
  5. ラップする側の親コンポーネント
  6. ラップされる側の子コンポーネント
  7. よくある実例-再利用可能なカードコンポーネント
  8. ラップする側の親コンポーネント
  9. ラップされる側の子コンポーネント
  10. childrenでよくあるつまずき

Reactでコンポーネントを他のコンポーネントに渡せるpropsのchildrenの使い方を紹介します。
childrenを上手に使うと、再利用性の高いコンポーネントを作成できます。

結論から言うと、childrenは「開始タグと終了タグの間に書いたもの」が自動で入る特別なpropsです。受け取る側は({ children }) => <div>{children}</div>のように分割代入で取り出し、好きな場所に{children}として置くだけで使えます。文字列・JSX・配列・別のコンポーネントなど、JSXの中に書けるものなら何でも渡せます。TypeScriptではchildren: React.ReactNodeと型を付けます。

childrenの基本的な使い方

他のコンポーネントをラップするコンポーネント(親)

import ChildComp from "./components/ChildComp";
import Container from "./components/Container";

const profile = [
  { name: "yamada", age: 78, country: "KOREA" },
  { name: "suzuki", age: 45, country: "USA" },
];

const Func = () => {
  return (
    <div>
      <Container title="Childrenについて" >
        {'children'}
        <ChildComp {...profile[0]} />
        <ChildComp {...profile[1]} />
      </Container>
    </div>
  );
};

export default Func;

このように、コンポーネントの中にコンポーネントを入れると、
propsと一緒にコンポーネントもpropsとして渡せます。
オブジェクトの配列にしておけば、いろいろなデータを出力できます。
{...profile[0]}のようにオブジェクトをまとめてpropsにする書き方は配列の中のオブジェクトのプロパティすべてをpropsとして渡す方法で解説しています。

また、childrenは属性として設定することもできます。

const profile = [
  { name: "yamada", age: 78, country: "KOREA" },
  { name: "suzuki", age: 45, country: "USA" },
];

const Func = () => {
  return (
    <div>
      <Container
        title="Childrenについて"
        children={
          [(<ChildComp key={profile[0].name} {...profile[0]} />), (<ChildComp key={profile[1].name} {...profile[1]} />)]
        }
      ></Container>
    </div>
  );
};

export default Func;

その場合、コンポーネントChildCompもJavaScriptのオブジェクトのため、上のように渡せます。
ただし、この場合はkey属性でユニークな値を指定する必要があります(指定しないと警告が出ます)。
よって、上のようにユニークな値のkeyを指定します。
なお、属性としてchildren={...}と書く方法は動作はしますが、React公式は推奨していません。通常はタグの間に書く形を使い、属性の書き方は「childrenも普通のpropsの1つ」と理解するための例と捉えてください。

const Container = ({ title, children }) => {
  console.log(children);
  return (
    <div className="container">
      <h3>{title}</h3>
      {children}
    </div>
  );
};

export default Container;

上のように、コンポーネントはchildrenという名前の引数で渡ってくるので、
{}で使えます。JSXの{}に書けるのは式だけという点はJSX内にJavaScriptの式を埋め込む方法を参考にしてください。

childrenとしてラップされる側のコンポーネント(子)

const ChildComp = ({ name, age, country }) => {
  return (
    <div>
      <h3>Name: {name}</h3>
      <p>Age: {age} </p>
      <p>Country: {country} </p>
    </div>
  );
};

export default ChildComp;

こちらは通常と同じです。

スポンサーリンク

渡すコンポーネントの順番を指定する方法

ラップする側の親コンポーネント

import ChildComp from "./components/ChildComp";
import Container from "./components/Container";

const profile = [
  { name: "yamada", age: 78, country: "KOREA" },
  { name: "suzuki", age: 45, country: "USA" },
];

const Func = () => {
  return (
    <div>
      <Container
        title="Childrenについて"
        children={[
          <ChildComp key={profile[0].name} {...profile[0]} />,
          <ChildComp key={profile[1].name} {...profile[1]} />,
        ]}
        zero={<ChildComp key={profile[0].name} {...profile[0]} />}
        first={<ChildComp key={profile[1].name} {...profile[1]} />}
      ></Container>
    </div>
  );
};

export default Func;

コンポーネントの順番を制御したい場合があります。
そのようなときは、上のように、それぞれのコンポーネントにそれぞれの属性をつけることで、可能になります。

ラップされる側の子コンポーネント

const Container = ({ title, children, zero, first }) => {
  console.log(children);
  return (
    <div className="container">
      <h3>{title}</h3>
      {children}
      {first}
      {zero}
    </div>
  );
};

export default Container;

上のようにしておけば、{zero}と{first}の順番を入れ替えて、表示するコンポーネントの順番を制御できます。
childrenが1つだけのときは要素そのもの、複数あるときは配列として渡ってきます。数を数えたり1つずつ加工したりする場合は、どちらでも同じ扱いができるReact.Children.map(children, fn)React.Children.count(children)を使います。

よくある実例-再利用可能なカードコンポーネント

ラップする側の親コンポーネント

import "./card.css";

const Card = ({ children, className, onClick }) => {
  return (
    <article className={`card ${className}`} onClick={onClick}>
      {children}
    </article>
  );
};

export default Card;

上のように、childrenのほかにclassNameを設定しておくと、たとえばaboutページとprofileページで同じカードを使いつつ、クラス名だけを変えて再利用できます。
また、onClickは渡されなければundefinedになるだけなので、onClickがある場合もない場合も上の書き方で問題ありません。

ラップされる側の子コンポーネント

import Card from "../../components/Card";
const data = [
    {id: 1, icon: <Icon1/>, title: 'タイトル1', desc: 'desc1'},
    {id: 2, icon: <Icon2/>, title: 'タイトル2', desc: 'desc2'},
    {id: 3, icon: <Icon3/>, title: 'タイトル3', desc: 'desc3'}
    ]

const About = () => {
  return (
    <section id="about">
      <div className="container about__container">
          <h2>About Me</h2>
          <div className="about__cards">
            {data.map((item) => (
              <Card key={item.id} className="about__card">
                <span className="about__card-icon">{item.icon}</span>
                <h5>{item.title}</h5>
                <small>{item.desc}</small>
              </Card>
            ))}
          </div>
          <p>
            ここに文章
          </p>
          <a href={CV} download className="btn primary">
            ダウンロード
          </a>
        </div>
    </section>
  );
};

export default About;

上のようにCardコンポーネント内で記述されたものがchildrenとして渡されます。
また、CardコンポーネントにclassNameをつけることで、静的な書き方であっても当然propsとしてclassNameが渡されます。
あとは、他のコンポーネントでも同じように、Cardコンポーネント内の記述とclassNameと、必要に応じてonClickを設定すれば、
再利用可能なカードコンポーネントになります。

childrenでよくあるつまずき

  • childrenが表示されない: 受け取る側で{children}を置き忘れているか、propsの分割代入の名前がchildren以外になっています。名前は固定です
  • 配列を渡したら「Each child in a list should have a unique key」の警告: 配列の各要素にkeyを付けます。タグの間に並べて書いた場合は不要です
  • TypeScriptで型エラー: type Props = { children: React.ReactNode }のように宣言します。JSX.Elementだと文字列や配列が渡せなくなります

childrenを使って開閉するパネルを組み立てる実例はReactでアクセシブルなアコーディオンを作る方法で紹介しています。

スポンサーリンク