メインコンテンツまでスキップ

Props

この章では、コンポーネントにデータを渡すためのPropsについて学びます。

この章で学ぶこと

  • Propsの基本的な使い方
  • 型定義の方法
  • オプショナルPropsとデフォルト値
  • childrenの使い方
備考

この章では03章で作成したプロジェクトを使用します。npm run devで開発サーバーを起動し、コードを書きながら学習を進めましょう。

Propsとは

Props(プロパティの略)は、親コンポーネントから子コンポーネントにデータを渡すための仕組みです。

Propsの基本

Propsを受け取る

function Greeting(props: { name: string }) {
return <h1>こんにちは、{props.name}さん!</h1>
}

Propsを渡す

function App() {
return <Greeting name="田中" />
}

分割代入

Propsは分割代入で受け取るのが一般的です。※分割代入については02章で解説しています。

function Greeting({ name }: { name: string }) {
return <h1>こんにちは、{name}さん!</h1>
}
なぜ{ name }: stringではダメなのか?

分割代入の型注釈で混乱しやすいポイントです。

// これはエラー
function Greeting({ name }: string) { }

型注釈は「分割代入する前のオブジェクト全体」を表します。

引数として受け取るもの 型注釈
↓ ↓
{ name } : { name: string }
↑ ↑
分割代入で取り出す 元のオブジェクトの型

展開すると、以下と同じ意味になります。

function Greeting(props: { name: string }) {
const { name } = props // propsはオブジェクトだから分割代入できる
return <h1>こんにちは、{name}さん!</h1>
}

{ name } は「オブジェクトから取り出す」構文なので、型もオブジェクト型でなければ矛盾します。

JSXのPropsはどうやって渡されるのか?

JSXで書いた属性は、オブジェクトに変換されて関数に渡されます。

// この呼び出しは...
<Greeting name="田中" />

// Reactが内部でこう変換している
Greeting({ name: "田中" })

つまり、JSXの属性がそのままオブジェクトのキーになります。

<UserCard name="田中" age={25} email="test@example.com" />
// ↓ 以下のオブジェクトとして渡される
// { name: "田中", age: 25, email: "test@example.com" }

型定義の分離

Propsの型は別途定義すると見やすくなります。

type GreetingProps = {
name: string
}

function Greeting({ name }: GreetingProps) {
return <h1>こんにちは、{name}さん!</h1>
}
備考

typeinterface のどちらを使うか

Props の型は typeinterface のどちらでも定義できますが、本書では type に統一しています。ユニオン型・インターセクション型・マップ型などを後から柔軟に組み合わせられる点で type のほうが応用範囲が広いためです。チームで統一されていればどちらでも構いません。

複数のProps

複数のPropsを渡すことができます。

type UserCardProps = {
name: string
age: number
email: string
}

function UserCard({ name, age, email }: UserCardProps) {
return (
<div>
<h2>{name}</h2>
<p>年齢: {age}</p>
<p>メール: {email}</p>
</div>
)
}

function App() {
return (
<UserCard
name="田中太郎"
age={25}
email="tanaka@example.com"
/>
)
}

export default App;

試してみよう: Propsを使ったコンポーネント

nameageをPropsとして受け取り、「こんにちは、〇〇さん(△△歳)!」と表示するGreetingコンポーネントを作ってみましょう。

ヒント
  1. type GreetingProps = { name: string; age: number }で型を定義
  2. 分割代入で{ name, age }を受け取る
  3. 数値のPropsは{}で囲んで渡す(age={25}
回答と解説
import './App.css'

type GreetingProps = {
name: string
age: number
}

function Greeting({ name, age }: GreetingProps) {
return <p>こんにちは、{name}さん({age}歳)!</p>
}

// 使用例
function App() {
return <Greeting name="田中" age={25} />
}

export default App

Propsの型を別途定義することで、コードの可読性が向上します。数値のPropsは{}で囲んで渡します(age={25})。文字列以外の値(数値、真偽値、オブジェクト、配列など)はすべて{}で囲む必要があります。

オプショナルProps

必須でないPropsは?を付けてオプショナルにします。

type ButtonProps = {
label: string
disabled?: boolean
}

function Button({ label, disabled = false }: ButtonProps) {
return (
<button disabled={disabled}>
{label}
</button>
)
}

// 使用例
function App() {
return (
<>
<Button label="送信" />
<Button label="送信" disabled />
</>
)
}

export default App;

デフォルト値

オプショナルPropsにはデフォルト値を設定できます。

type AlertProps = {
message: string
type?: 'info' | 'warning' | 'error'
}

function Alert({ message, type = 'info' }: AlertProps) {
const colors = {
info: 'blue',
warning: 'orange',
error: 'red'
}

return (
<div style={{ color: colors[type] }}>
{message}
</div>
)
}

// 使用例
function App() {
return (
<>
<Alert message="情報メッセージです" />
<Alert message="警告メッセージです" type="warning" />
<Alert message="エラーメッセージです" type="error" />
</>
)
}

export default App;

試してみよう: オプショナルPropsを追加

先ほど作成したGreetingコンポーネントにlanguageプロップを追加してみましょう。デフォルトは日本語で、"en"が渡されたら英語で挨拶するようにします。

ヒント
  1. language?: 'ja' | 'en'でユニオン型のオプショナルPropsを定義
  2. デフォルト値= 'ja'を分割代入で設定
  3. if文や三項演算子で言語に応じた表示を切り替え
回答と解説
import './App.css'

type GreetingProps = {
name: string
age: number
language?: 'ja' | 'en'
}

function Greeting({ name, age, language = 'ja' }: GreetingProps) {
if (language === 'en') {
return <p>Hello, {name} ({age} years old)!</p>
}
return <p>こんにちは、{name}さん({age}歳)!</p>
}

// 使用例
function App() {
return (
<>
<Greeting name="田中" age={25} />
<Greeting name="Tanaka" age={25} language="en" />
</>
)
}

export default App

?を付けるとオプショナルになり、デフォルト値を設定できます。ユニオン型('ja' | 'en')を使うと、許可される値を制限でき、TypeScriptが型チェックしてくれます。

children Props

コンポーネントタグの間に記述した内容は、childrenとして渡されます。

React.ReactNodeとは?

React.ReactNodeは、Reactがレンダリングできるすべての型を表します。

// 簡略化したイメージ(実際の型定義はもう少し複雑です)
type ReactNode =
| ReactElement // <Component /> や <div> など
| string // "テキスト"
| number // 123
| boolean // true/false(何も表示されない)
| null // 何も表示されない
| undefined // 何も表示されない
| ReactNode[] // 配列

childrenの型には通常React.ReactNodeを使用します。

type CardProps = {
title: string
children: React.ReactNode
}

function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">
{children}
</div>
</div>
)
}

function App() {
return (
<Card title="お知らせ">
<p>本日のセールは終了しました。</p>
<p>明日もお待ちしています!</p>
</Card>
)
}

export default App;

試してみよう: childrenを使ったCardコンポーネント

titlechildrenを受け取るCardコンポーネントを作成してみましょう。カードにはボーダーとパディングを付けて、タイトルと中身を表示します。

ヒント
  1. children: React.ReactNodeを型定義に追加
  2. {children}で子要素を表示
  3. インラインスタイルで装飾(border, padding, borderRadius
回答と解説
import './App.css'

type CardProps = {
title: string
children: React.ReactNode
}

function Card({ title, children }: CardProps) {
return (
<div style={{ border: '1px solid #ccc', padding: '16px', borderRadius: '8px' }}>
<h2>{title}</h2>
<div>{children}</div>
</div>
)
}

// 使用例
function App() {
return (
<Card title="お知らせ">
<p>本日は休業日です。</p>
<p>明日からよろしくお願いします。</p>
</Card>
)
}

export default App

childrenを使うと、コンポーネントタグの間に記述した内容を受け取れます。これにより、柔軟で再利用性の高いレイアウトコンポーネントが作れます。HTMLの<div><section>のように、中身を自由に変えられるコンポーネントを作る際に重宝します。

オブジェクトのスプレッド

オブジェクトのPropsはスプレッド構文で展開できます。

type UserProps = {
name: string
age: number
email: string
}

function UserProfile(props: UserProps) {
return (
<div>
<p>名前: {props.name}</p>
<p>年齢: {props.age}</p>
<p>メール: {props.email}</p>
</div>
)
}

function App() {
const user = {
name: '田中',
age: 30,
email: 'tanaka@example.com'
}

return <UserProfile {...user} />
}

export default App;

Propsの原則

1. Propsは読み取り専用

Propsは子コンポーネント内で変更してはいけません。

// NG: Propsを変更しようとしている
function Greeting({ name }: { name: string }) {
name = '山田' // 構文上は通ってしまうが、やってはいけない
return <h1>こんにちは、{name}さん!</h1>
}
警告

この再代入は実はTypeScriptのエラーになりません(分割代入したnameはただのローカル変数のため)。しかし、Reactのコンポーネントは「同じPropsを受け取ったら同じ結果を返す」純粋な関数であるべきで、Propsの書き換えはこの前提を壊します。これは02章で解説した「イミュータブル(不変)」の原則に基づいています。表示を変えたいときはPropsを書き換えるのではなく、親に別のPropsを渡してもらうか、次章で学ぶStateを使います。

2. 単方向データフロー

データは常に親から子へ流れます。これにより、データの流れが予測しやすくなります。

関数のProps

関数もPropsとして渡せます。これはイベントハンドラーで頻繁に使用されます。※イベントハンドリングの詳細は08章で解説します。

type ButtonProps = {
label: string
onClick: () => void
}

function Button({ label, onClick }: ButtonProps) {
return <button onClick={onClick}>{label}</button>
}

function App() {
const handleClick = () => {
console.log('クリックされました')
}

return <Button label="クリック" onClick={handleClick} />
}

export default App;

ref も Props として渡せる(React 19+)

React 19 からは、ref を通常の Props と同じように関数コンポーネントで受け取れるようになりました。以前の React(18 以下)では forwardRef で包む必要がありましたが、その手間がなくなっています。

// React 19+: refをそのままpropsから受け取れる
type InputProps = {
label: string
ref?: React.Ref<HTMLInputElement>
}

function LabeledInput({ label, ref }: InputProps) {
return (
<label>
{label}
<input ref={ref} />
</label>
)
}

useRefref 属性そのものの使い方は 12章で詳しく解説するため、ここでは「ref は Props の一種として扱える」という点だけ押さえておけば十分です。

まとめ

  • Propsは親から子へデータを渡す仕組み
  • TypeScriptで型を定義して安全に使用
  • オプショナルPropsにはデフォルト値を設定できる
  • childrenでコンポーネントの中身を渡せる
  • React 19 からは ref も Props として受け取れる(forwardRef 不要)
  • Propsは読み取り専用、データは単方向

次の章では、コンポーネント内部の状態を管理するStateについて学びます。

次に読む