目次
4 章

コンポーネントと props

関数コンポーネントを作り、props でデータを流す基本パターンを見ていきます。17 章で TypeScript によって固めるインターフェースの土台です。

3 章で JSX の構文を見ました。その中で App という名前の関数がよく登場しましたが、実はこの App こそが React の最も重要な単位であるコンポーネント(Component) です。本章ではコンポーネントとは何で、どう作るのか、そしてコンポーネント同士でデータをやり取りする通路である props を見ていきます。

本章の props モデルは 17 章(props と children の型付け)で TypeScript によってもう一度固められ、24 章(Server vs Client Components)では server → client コンポーネントへ props が渡るときのシリアライズ条件まで拡張されます。本章でコンポーネント / props の基本原理をしっかり押さえておけば、その後の章がはるかに軽く読めます。

コンポーネントはなぜ必要なのか #

画面全体を 1 つの巨大な関数にすべて書く場面を想像してみてください。ヘッダ、サイドバー、メインコンテンツ、フッタ、ボタン、入力欄。コードはすぐに数百、数千行になり、どこを直せばいいかを探すことすら難しくなります。同じ形のボタンが画面に 10 個あれば、同じコードを 10 回書かなければなりません。

React はこの問題をコンポーネントという概念で解決します。コンポーネントは画面の 1 つの断片を表現する再利用可能な単位です。ヘッダ、ボタン、カード、入力欄のような画面要素をそれぞれ 1 つのコンポーネントとして作っておき、必要な場所で HTML タグのように呼び出して使います。

最初のコンポーネントを作る #

React においてコンポーネントは結局のところ JSX を返す JavaScript 関数です。3 章の間ずっと見てきた App もそうです。

src/App.jsx
function Greeting() {
  return <h1>こんにちはReact!</h1>;
}

function App() {
  return (
    <div>
      <Greeting />
    </div>
  );
}

export default App;

Greeting という新しい関数を定義し、App の中で HTML タグのように <Greeting /> として使いました。関数 1 つがそのまま 1 つのコンポーネントになります。

注記
コンポーネント名は必ず大文字で始まる必要があります。<greeting /> のように小文字で始めると、React はこれを普通の HTML タグとして認識して、意図と異なる動作になります。命名規則: PascalCase(UserCardLoginButton)が標準です。

コンポーネントを別ファイルに分ける #

コンポーネントが増えてきたら、1 つのファイルにすべて書くより、ファイルごとに分ける方がよいです。ふつうはコンポーネント 1 つにつきファイル 1 つです。

src/Greeting.jsx というファイルを新しく作って:

src/Greeting.jsx
function Greeting() {
  return <h1>こんにちはReact!</h1>;
}

export default Greeting;

App.jsx ではこのように読み込みます。

src/App.jsx
import Greeting from './Greeting';

function App() {
  return (
    <div>
      <Greeting />
    </div>
  );
}

export default App;

export default でエクスポートし import で受け取るという、一般的な JavaScript モジュールのパターンそのままです。ファイル拡張子(.jsx)は省略しても Vite が自動で見つけてくれます。

ヒント
ファイル名もコンポーネント名と同じく PascalCase で付けるのが慣例です。Greeting.jsxUserCard.jsx のような形です。フォルダ構造はプロジェクトごとに違いますが、小さなプロジェクトでは src/components/ 以下に集めておくのが一般的です。

コンポーネントにデータを渡す — props #

Greeting コンポーネントは常に「こんにちは、React!」しか出力しません。ユーザごとに違う挨拶を見せたい場合はどうすればよいでしょうか。props を使えばできます。

props はコンポーネントの引数のような概念です。親が子コンポーネントにデータを渡すときに使います。HTML 属性を書く感覚で自然に書けます。

src/App.jsx
import Greeting from './Greeting';

function App() {
  return (
    <div>
      <Greeting name="太郎" />
      <Greeting name="花子" />
      <Greeting name="次郎" />
    </div>
  );
}

export default App;

Greeting を 3 回使いながら、毎回違う name の値を渡しました。次は Greeting コンポーネントがこの値を受け取れるように修正します。

src/Greeting.jsx
function Greeting(props) {
  return <h1>こんにちは{props.name}さん!</h1>;
}

export default Greeting;

関数の最初の引数として props というオブジェクトが入ってきます。親が渡したすべての属性がこのオブジェクトのプロパティとして含まれます。name="太郎" で渡したので、props.name'太郎' です。

画面には次のように出力されます。

画面出力
こんにちは、太郎さん!
こんにちは、花子さん!
こんにちは、次郎さん!

同じコンポーネントが props だけ変えて 3 回再利用されたわけです。これがコンポーネントの核となる価値です。

さまざまな型の props #

props には文字列だけでなく、数値、真偽値、配列、オブジェクト、さらには関数まで渡せます。文字列以外の値は波括弧 { } で包む必要がある、という点だけ覚えておいてください。

src/App.jsx
function App() {
  const user = { name: '太郎', email: 'taro@example.com' };

  return (
    <UserCard
      name="太郎"
      age={30}
      isAdmin={true}
      hobbies={['読書', 'コーディング', '旅行']}
      profile={user}
    />
  );
}
src/UserCard.jsx
function UserCard(props) {
  return (
    <div>
      <h2>{props.name}({props.age})</h2>
      {props.isAdmin && <p>管理者権限があります</p>}
      <p>メール: {props.profile.email}</p>
      <p>趣味: {props.hobbies.join(', ')}</p>
    </div>
  );
}

export default UserCard;

文字列は引用符で(name="太郎")、それ以外の JavaScript の値は波括弧で(age={30})渡す、と覚えれば十分です。

分割代入できれいに受け取る #

props.nameprops.age のように毎回 props. を付けるのが面倒なら、JavaScript の分割代入(destructuring) を使います。

src/Greeting.jsx
function Greeting({ name }) {
  return <h1>こんにちは{name}さん!</h1>;
}

export default Greeting;

引数の部分でそのまま分解して受け取れば、関数本体では name だけで使えるのでコードが短くなります。複数の props ならそのまま並べます。

src/UserCard.jsx
function UserCard({ name, age, isAdmin, hobbies, profile }) {
  return (
    <div>
      <h2>{name}({age})</h2>
      {isAdmin && <p>管理者権限があります</p>}
      <p>メール: {profile.email}</p>
      <p>趣味: {hobbies.join(', ')}</p>
    </div>
  );
}

export default UserCard;

実際の React コードではこの形がはるかによく見られます。本書もこれ以降このスタイルで書きます。

既定値を指定する #

prop が渡されない可能性がある場面では、分割代入と一緒に既定値を指定できます。

src/Greeting.jsx
function Greeting({ name = 'ゲスト' }) {
  return <h1>こんにちは{name}さん!</h1>;
}

<Greeting /> のように name なしで使うと、自動的に 'ゲスト' が使われます。

children — コンポーネントの間に入る子要素 #

ここまでは <Greeting /> のように自己閉じの形でだけコンポーネントを使ってきました。ところが HTML のように、開きタグと閉じタグの間に何かを入れたい場面もあります。

src/App.jsx
import Card from './Card';

function App() {
  return (
    <Card>
      <h2>お知らせ</h2>
      <p>本日は休業日です</p>
    </Card>
  );
}

このとき <Card></Card> の間に入った内容は、自動的に children という特別な prop に入れられて渡されます。

src/Card.jsx
function Card({ children }) {
  return (
    <div className="card" style={{ border: '1px solid #ccc', padding: '16px', borderRadius: '8px' }}>
      {children}
    </div>
  );
}

export default Card;

Card コンポーネントは中に何が入るか知りませんが、children をその位置に出力するだけで構いません。このパターンはレイアウト(Card、Modal、Layout など)やラッパー(Wrapper) 性格のコンポーネントで非常によく使われます。

17 章(props と children の型付け)では children の正確な型 — ReactNodeReactElementJSX.Element の違い — を扱います。

props は読み取り専用です #

最後に最も重要なルールです。コンポーネントは自分が受け取った props を絶対に変更してはなりません。 次は悪い例です。

悪い例
function Greeting({ name }) {
  name = name.toUpperCase(); // 🚫 props を直接変更
  return <h1>こんにちは{name}さん!</h1>;
}

props は親から流れ落ちてくるデータのコピーであり、子が自由に書き換えてよい値ではありません。加工が必要なら新しい変数に入れて使います。

よい例
function Greeting({ name }) {
  const upperName = name.toUpperCase();
  return <h1>こんにちは{upperName}さん!</h1>;
}
注記

このルールは単なるスタイルガイドではなく、React の動作原理に直結しています。React はデータが上から下へ(親 → 子)、一方向に流れるという前提の上に作られているため、子が受け取った props を勝手に書き換えるとデータの流れが乱れ、デバッグが非常に難しくなります。親のデータを変える必要がある場面では、次の 5 章で学ぶ state とコールバック関数を活用します。

このルールは 24 章(Server vs Client Components)でもう一度重要になります。Server Component が Client Component に props を渡すとき、その値はシリアライズ(JSON への変換)可能な値だけが許されます。関数 / Date / Map などは直接渡せません。

自分でやってみる #

これまで作った src/App.jsx を次の構造に変えてみてください。

src/UserCard.jsx を新しく作って:

src/UserCard.jsx
function UserCard({ name, age, hobbies }) {
  return (
    <div style={{ border: '1px solid #ccc', padding: '12px', margin: '8px', borderRadius: '8px' }}>
      <h2>{name}({age})</h2>
      <p>趣味: {hobbies.join(', ')}</p>
    </div>
  );
}

export default UserCard;

src/App.jsx はこんなふうに:

src/App.jsx
import UserCard from './UserCard';

function App() {
  return (
    <>
      <h1>会員一覧</h1>
      <UserCard name="太郎" age={30} hobbies={['読書', 'コーディング']} />
      <UserCard name="花子" age={28} hobbies={['旅行', '料理', '写真']} />
      <UserCard name="次郎" age={35} hobbies={['ゲーム']} />
    </>
  );
}

export default App;

保存すると 3 人分の会員カードが画面に描かれます。同じ UserCard コンポーネントが props だけ変わりながら 3 回再利用されたわけです。

練習問題 #

  1. UserCard に新しい prop isOnline(boolean)を追加し、true のときだけ名前の横に 🟢 絵文字を表示するようにしてみてください。3 人のうち花子だけ isOnline={true} を渡します。条件付き表示には {isOnline && '🟢'} のパターンを使います(7 章でより深く扱います)。
  2. Card という新しいコンポーネントを作り、children で受け取った内容を枠で囲まれたボックスの中に表示するように書いてみてください。その後 App<Card><h2>お知らせ</h2><p>本サイトはメンテナンス中です。</p></Card> の形で使います。
  3. Greeting コンポーネントに greeting prop を追加し、既定値 'こんにちは' を与えてみてください。<Greeting name="太郎" /><Greeting name="John" greeting="Hello" /> の 2 つの呼び出しがそれぞれ「こんにちは、太郎さん!」と「Hello、Johnさん!」を出力するようにします。

一行まとめ: コンポーネントは JSX を返す JavaScript 関数だ。親は props で子にデータを渡し、子はそれを読み取り専用で使う。分割代入 / 既定値 / children パターンが日常のすべてだ。props は絶対に変更しない。

次の章 #

ここまでのコンポーネントはすべて静的でした。一度描かれたら二度と変わりませんでした。しかし実際のアプリはユーザの入力、時間、サーバの応答に応じて絶えず姿を変えます。次の第 5 章 State と useStateでは、コンポーネントが変わりうるデータを扱う方法、つまり state の概念と useState フックを学びます。

X