コンポーネントに props を渡す

React コンポーネントは互いにやりとりをする際に props というものを使います。親コンポーネントは子コンポーネントに props を渡すことで情報を伝えることができるのです。props は HTML の属性と似ていると思われるかもしれませんが、props ではオブジェクトや配列、関数などのあらゆる JavaScript の値を渡すことができます。

このページで学ぶこと

  • コンポーネントに props を渡す方法
  • コンポーネントから props を読み出す方法
  • props のデフォルト値を指定する方法
  • コンポーネントに JSX を渡す方法
  • props は時間とともに変化する

お馴染みの props

props とは JSX タグに渡す情報のことです。例えば classNamesrcaltwidthheight は、<img> に渡すことのできる props の例です。

function Avatar() {
  return (
    <img
      className="avatar"
      src="https://i.imgur.com/1bX5QH6.jpg"
      alt="Lin Lanying"
      width={100}
      height={100}
    />
  );
}

export default function Profile() {
  return (
    <Avatar />
  );
}

<img> に渡すことができる props の種類は事前に決められています(ReactDOM は HTML 標準に準拠しています)。しかし <Avatar> のようなあなた独自のコンポーネントの場合は、任意の props を渡してそれをカスタマイズできます。以下でやり方を説明します。

コンポーネントに props を渡す

以下のコードでは、Profile コンポーネントは子コンポーネントである <Avatar> に何の props も渡していません。

export default function Profile() {
return (
<Avatar />
);
}

以下の 2 ステップで、Avatar に props を与えることができます。

Step 1: 子コンポーネントに props を渡す

まず、Avatar に何か props を渡します。例えば person(オブジェクト)と size(数値)を渡してみましょう。

export default function Profile() {
return (
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
);
}

補足

もし person= の後にある二重の波括弧が分からない場合、これが JSX 波括弧の中にある単なるオブジェクトであるということを思い出してください。

これでこの props を Avatar コンポーネント内から読み出せるようになります。

Step 2: 子コンポーネントから props を読み出す

これらの props を読み出すには、function Avatar の直後の ({}) 内で、コンマで区切って person, size のように名前を指定します。これにより Avatar のコード内で変数と同じようにこれらの props が使えるようになります。

function Avatar({ person, size }) {
// person and size are available here
}

Avatar 内に、personsize を使って何かをレンダーするロジックを書き加えれば完成です。

これで Avatar に様々な props を渡すことで様々に表示を変えられるようになりました。実際に値をいじってみましょう!

import { getImageUrl } from './utils.js';

function Avatar({ person, size }) {
  return (
    <img
      className="avatar"
      src={getImageUrl(person)}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}

export default function Profile() {
  return (
    <div>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi', 
          imageId: 'YfeOqp2'
        }}
      />
      <Avatar
        size={80}
        person={{
          name: 'Aklilu Lemma', 
          imageId: 'OKS67lh'
        }}
      />
      <Avatar
        size={50}
        person={{ 
          name: 'Lin Lanying',
          imageId: '1bX5QH6'
        }}
      />
    </div>
  );
}

props のおかげで、親と子のコンポーネントを独立して考えることができるようになります。例えば、Profilepersonsize を変更するときに Avatar 内でどう使われるかを気にしないでよくなります。同様に、Avatar がこれらの props をどのように使うのかは、Profile を見ずに変更できるようになります。

props とは自分で調整できるコントローラの「ツマミ」のようなものです。関数における引数と同じ役割を果たしています。むしろ、props があなたのコンポーネントの唯一の引数です! React コンポーネントは props というオブジェクトを唯一の引数として受け取っているのです。

function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}

通常は props オブジェクト全体を必要とすることはないため、個々の props へと分割代入します。

落とし穴

props を宣言する際は () の中に、{} という波括弧のペアを書き忘れないようにしましょう。

function Avatar({ person, size }) {
// ...
}

この構文は “分割代入 (destructuring)” と呼ばれるもので、関数の引数からプロパティを読み出す以下のようなコードと同等です。

function Avatar(props) {
let person = props.person;
let size = props.size;
// ...
}

props のデフォルト値を指定する

props に、値が渡されなかった場合にフォールバックとして使うデフォルト値を指定したい場合、分割代入の中でパラメータ名の直後に = とデフォルト値を書くことができます。

function Avatar({ person, size = 100 }) {
// ...
}

これで、size プロパティを指定せずに <Avatar person={...} /> のようにレンダーされた場合、size100 にセットされます。

このデフォルト値は size がない場合や size={undefined} を渡した場合にのみ使用されます。size={null}size={0} を渡した場合にはデフォルト値は使われません

JSX スプレッド構文で props を転送する

ときに、props の受け渡しが冗長な繰り返しになってしまうことがあります。

function Profile({ person, size, isSepia, thickBorder }) {
return (
<div className="card">
<Avatar
person={person}
size={size}
isSepia={isSepia}
thickBorder={thickBorder}
/>
</div>
);
}

繰り返しの多いコードが悪いわけではありませんし、その方が読みやすいこともあるでしょう。しかし簡潔であることに価値があることもあります。この ProfileAvatar に対してやっているように、コンポーネントの中には props をそのまま子コンポーネントに転送するものがあります。Profile は props を直接的に使っているわけではありませんので、以下のような「スプレッド」構文を使って短く書く方が理にかなっているかもしれません。

function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}

これにより、Profile に渡された props を、個々の名前を列挙することなくすべて Avatar に転送できます。

スプレッド構文は慎重に使ってください。この構文をあらゆるコンポーネントで使っているなら、何かが間違っています。多くの場合は、コンポーネントを分割して JSX として children を渡すべきというサインです。今からこれについて述べていきます。

children として JSX を渡す

ブラウザの組み込みタグをネストすることはよくありますね。

<div>
<img />
</div>

同様にして独自コンポーネントもネストしたくなることがあります。

<Card>
<Avatar />
</Card>

このように JSX タグ内でコンテンツをネストした場合、親側のコンポーネントはその中身を children という props として受け取ります。例えば以下の Card コンポーネントは、<Avatar /> がセットされた children プロパティを受け取って、ラッパ div 要素の内部にそれをレンダーしています。

import Avatar from './Avatar.js';

function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

export default function Profile() {
  return (
    <Card>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi',
          imageId: 'YfeOqp2'
        }}
      />
    </Card>
  );
}

<Card> 内の <Avatar> を何かテキストに置き換えてみて、ネストされているどんなコンテンツでも Card コンポーネントは囲んで表示できるということを確かめてください。中に何が表示されるかあらかじめ知っておく必要はありません。このような柔軟なパターンは、様々なところで目にすることになるでしょう。

children プロパティを有するコンポーネントには、親に任意の JSX で「埋めて」もらうための「穴」が開いている、と考えることができます。children は、パネルやグリッドのような視覚的に何かを囲む要素に使うことができます。

A puzzle-like Card tile with a slot for "children" pieces like text and Avatar

Illustrated by Rachel Lee Nabors

props は時間とともに変化する

以下の Clock コンポーネントは親コンポーネントから colortime という 2 つの props を受け取っています。(親コンポーネントのコードは、まだ解説していない state を使っているため省略しています。)

以下の選択ボックスでカラーを変えてみてください。

export default function Clock({ color, time }) {
  return (
    <h1 style={{ color: color }}>
      {time}
    </h1>
  );
}

この例は、コンポーネントは時間経過とともに別の props を受け取る可能性があるということを示しています。props は常に固定だとは限らないのです! ここでは time プロパティは毎秒変化していますし、color プロパティもあなたが別の色を選択するたびに変化します。props とはコンポーネントの最初の時点ではなく、任意の時点でのコンポーネントのデータを反映するものなのです。

しかし、props はイミュータブル (immutable) です。これは「不変な」という意味のコンピュータサイエンス用語です。コンポーネントの props が(例えばユーザ操作や新しいデータの到着に反応して)変わらないといけない場合、親のコンポーネントに別の props、つまり新しいオブジェクトを渡してもらう必要があります! 古い props は忘れられ、使われていたメモリは JavaScript エンジンがそのうち回収します。

「props の書き換え」をしようとしてはいけません。(上記のカラー選択のように)ユーザの入力に反応する必要がある場合は、「state のセット」が必要です。これについては state:コンポーネントのメモリで学びます。

まとめ

  • props を渡すには HTML で属性を書くのと同様のやり方で JSX 内に書く。
  • props を読み出すには、function Avatar({ person, size }) のような分割代入構文を使う。
  • size = 100 のようなデフォルト値を指定でき、これは props がない場合や undefined の場合に使われる。
  • <Avatar {...props} /> のような JSX スプレッド構文ですべての props を転送できるが、多様は禁物!
  • <Card><Avatar /></Card> のようなネストされた JSX を書くと Card コンポーネントの children プロパティになる。
  • props とはある時点での読み取り専用のスナップショットである。レンダー毎に新しいバージョンの props を受け取る。
  • props を書き換えることはできない。インタラクティブ性が必要な場合は state を設定する必要がある。

チャレンジ 1/3:
コンポーネント抽出

以下の Gallery コンポーネントには 2 名のプロフィール用にとても似たマークアップが含まれてしまっています。ここから Profile というコンポーネントを抽出してコードの重複を減らしてください。どんな props を渡すようにするのかは、自分で決めてください。

import { getImageUrl } from './utils.js';

export default function Gallery() {
  return (
    <div>
      <h1>Notable Scientists</h1>
      <section className="profile">
        <h2>Maria Skłodowska-Curie</h2>
        <img
          className="avatar"
          src={getImageUrl('szV5sdG')}
          alt="Maria Skłodowska-Curie"
          width={70}
          height={70}
        />
        <ul>
          <li>
            <b>Profession: </b> 
            physicist and chemist
          </li>
          <li>
            <b>Awards: 4 </b> 
            (Nobel Prize in Physics, Nobel Prize in Chemistry, Davy Medal, Matteucci Medal)
          </li>
          <li>
            <b>Discovered: </b>
            polonium (chemical element)
          </li>
        </ul>
      </section>
      <section className="profile">
        <h2>Katsuko Saruhashi</h2>
        <img
          className="avatar"
          src={getImageUrl('YfeOqp2')}
          alt="Katsuko Saruhashi"
          width={70}
          height={70}
        />
        <ul>
          <li>
            <b>Profession: </b> 
            geochemist
          </li>
          <li>
            <b>Awards: 2 </b> 
            (Miyake Prize for geochemistry, Tanaka Prize)
          </li>
          <li>
            <b>Discovered: </b>
            a method for measuring carbon dioxide in seawater
          </li>
        </ul>
      </section>
    </div>
  );
}