コンポーネントへのPropsの受け渡し

Reactのコンポーネントは、互いに通信するためにpropsを使用します。すべての親コンポーネントは、子コンポーネントにpropsを与えることで、何らかの情報を渡すことができます。propsはHTMLの属性を連想させるかもしれませんが、オブジェクト、配列、関数など、任意のJavaScriptの値をpropsとして渡すことができます。

以下を学びます

  • コンポーネントに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を与えることができます。

ステップ1:子コンポーネントにpropsを渡す

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

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

person=の後の二重波括弧が混乱する場合は、JSXの波括弧内の単なるオブジェクトであることを思い出してください。

これで、これらのpropsをAvatarコンポーネント内で読み取ることができます。

ステップ2:子コンポーネント内でpropsを読み取る

function Avatarの直後の({})の中に、コンマで区切られたperson, sizeという名前をリストすることで、これらのpropsを読み取ることができます。これにより、変数を使用するのと同じように、Avatarコード内でそれらを使用できます。

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

Avatarに、personsize propsをレンダリングに使用するロジックを追加すれば、完了です。

これで、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を使用すると、親コンポーネントと子コンポーネントを独立して考えることができます。たとえば、Profile 内で personsize のpropsを変更しても、Avatar がそれらをどのように使用するかを考慮する必要はありません。同様に、Avatar がこれらのpropsをどのように使用するかを、Profile を見ることなく変更できます。

Propsは調整できる「つまみ」のように考えることができます。これらは、関数に対する引数と同じ役割を果たします。実際、propsはコンポーネントへの唯一の引数です! Reactコンポーネント関数は、単一の引数である props オブジェクトを受け取ります。

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

通常、props オブジェクト全体は必要ないので、個々のpropsに分割(destructure)します。

落とし穴

propsを宣言するときは、() の内側の {} の波括弧のペアを忘れないでください。

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

この構文は「分割代入」と呼ばれ、関数パラメータからプロパティを読み取るのと同じです。

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

propsのデフォルト値の指定

値が指定されていない場合にフォールバックするデフォルト値をpropに指定したい場合は、分割代入を使用して、パラメータの直後に = とデフォルト値を記述できます。

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

これで、<Avatar person={...} />size propなしでレンダリングされると、size100 に設定されます。

デフォルト値は、size propがない場合、または 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を子に転送します。それらはpropsを直接使用しないため、より簡潔な「スプレッド」構文を使用する方が理にかなっている場合があります。

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

これにより、各propsの名前をリストせずに、Profile のすべてのpropsが Avatar に転送されます。

スプレッド構文は控えめに使用してください。それを他のすべてのコンポーネントで使用している場合、何か問題があります。多くの場合、コンポーネントを分割し、子をJSXとして渡す必要があることを示しています。次のセクションで詳しく説明します!

JSXを子として渡す

組み込みブラウザタグをネストすることは一般的です。

<div>
<img />
</div>

同じように独自のコンポーネントをネストしたい場合があります。

<Card>
<Avatar />
</Card>

JSXタグ内にコンテンツをネストすると、親コンポーネントはそのコンテンツを children というpropsで受け取ります。たとえば、以下の Card コンポーネントは、<Avatar /> に設定された children propを受け取り、ラッパー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 propを持つコンポーネントは、親コンポーネントが任意のJSXで「埋める」ことができる「穴」を持っていると考えることができます。視覚的なラッパー(パネル、グリッドなど)には、children propをよく使用します。

時間の経過に伴うpropsの変化

以下の Clock コンポーネントは、親コンポーネントから colortime の2つのpropsを受け取ります。(親コンポーネントのコードは、まだ詳しく説明しない状態を使用しているため、省略されています。)

以下のセレクトボックスで色を変更してみてください。

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

この例は、コンポーネントが時間の経過とともに異なるpropsを受け取る可能性があることを示しています。propsは常に静的ではありません!ここでは、time propは毎秒変化し、color propは別の色を選択すると変化します。propsは、コンポーネントのデータを開始時だけでなく、任意の時点で反映します。

ただし、propsはイミュータブルです。これはコンピュータサイエンスの用語で、「変更不能」を意味します。コンポーネントがpropsを変更する必要がある場合(例えば、ユーザーの操作や新しいデータに応答する場合)、親コンポーネントに異なるpropsを渡すように「要求」する必要があります。それは新しいオブジェクトです!古いpropsは破棄され、最終的にJavaScriptエンジンがそれらが占有していたメモリを再利用します。

「propsを変更」しようとしないでください。ユーザーの入力に応答する必要がある場合(例えば、選択した色を変更する場合)、State: A Component’s Memory(ステート:コンポーネントの記憶)で学ぶことができる「ステートを設定」する必要があります。

まとめ

  • propsを渡すには、HTML属性と同じようにJSXに追加します。
  • propsを読み取るには、function Avatar({ person, size }) の分割代入構文を使用します。
  • size = 100 のようにデフォルト値を指定できます。これは、propsが欠落している場合やundefined の場合に使用されます。
  • <Avatar {...props} /> JSXスプレッド構文を使用して、すべてのpropsを転送できますが、過度に使用しないでください!
  • <Card><Avatar /></Card> のようなネストされたJSXは、Card コンポーネントのchildren propとして表示されます。
  • propsは、時間の経過とともに読み取り専用のスナップショットです。すべてのレンダリングは、propsの新しいバージョンを受け取ります。
  • propsを変更することはできません。インタラクティビティが必要な場合は、ステートを設定する必要があります。

チャレンジ 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>
  );
}