Props
コンポーネントに外からデータを渡す仕組み「Props」を学びます。コンポーネントを汎用的に再利用できるようになります。
このチャプターで学ぶこと
- Propsを使ってコンポーネントに値を渡せる
- 分割代入(デストラクチャリング)でPropsを受け取れる
- デフォルト値を設定できる
- childrenプロップを使って子要素を受け取れる
- ProfileコンポーネントをPropsで汎用化して複数人のデータを表示できる
Props
前のチャプターで Profile.jsx に「山田太郎」という名前をハードコードしました。このままでは同じコンポーネントを使って別の人のプロフィールを表示できません。そこで登場するのが Props(プロップス) です。
Propsとは
PropsはProperties(プロパティ)の略です。コンポーネントに外からデータを渡す仕組みです。
Laravelのbladeコンポーネントでいえば、@component('card', ['title' => '記事タイトル']) のように変数を渡す感覚に近いです。HTML要素の属性のようにコンポーネントにデータを渡せます。
// 使う側(親コンポーネント)
function App() {
return (
<div>
{/* name と age を Props として渡す */}
<Profile name="山田太郎" age={25} />
</div>
)
}
// 受け取る側(子コンポーネント)
function Profile(props) {
// props は { name: '山田太郎', age: 25 } というオブジェクト
return (
<div>
<h2>{props.name}</h2>
<p>{props.age}歳</p>
</div>
)
}
コンポーネントは引数 props を受け取ります。props は渡された全データが入ったJavaScriptオブジェクトです。
Propsのデータ型
文字列は引用符 "" で、それ以外はすべて {} で渡します。
function App() {
const userData = { hobby: 'プログラミング' }
return (
<UserCard
name="山田太郎" // 文字列は "" で渡せる
age={25} // 数値は {} が必要
isAdmin={true} // 真偽値も {}
hobbies={['読書', '映画']} // 配列も {}
profile={userData} // オブジェクトも {}
onClose={() => {}} // 関数も {} で渡せる(後のチャプターで詳しく)
/>
)
}
isActive={true} は isActive と短縮できます。真偽値のPropsを属性として書くと true になります。Laravelのコンポーネント属性と同じ感覚です。
分割代入(デストラクチャリング)で受け取る
毎回 props.name、props.age と書くのは冗長です。JavaScriptの分割代入を使うとすっきり書けます。
// props オブジェクトをそのまま受け取る(冗長)
function Profile(props) {
return (
<div>
<h2>{props.name}</h2>
<p>{props.age}歳</p>
<p>{props.bio}</p>
</div>
)
}
// 分割代入で受け取る(こちらが一般的)
function Profile({ name, age, bio }) {
return (
<div>
<h2>{name}</h2>
<p>{age}歳</p>
<p>{bio}</p>
</div>
)
}
引数部分で { name, age, bio } と書くだけです。Propsがすぐに変数として使えてスッキリします。実際の現場のコードはほぼこの書き方です。
デフォルト値を設定する
Propsが渡されなかった場合のデフォルト値を設定できます。これも分割代入の機能を使います。
function Profile({ name, age, bio = '自己紹介文はまだ書いていません' }) {
return (
<div>
<h2>{name}</h2>
{/* age が渡されていない場合は表示しない */}
{age && <p>{age}歳</p>}
<p>{bio}</p>
</div>
)
}
function App() {
return (
<div>
{/* bio を渡さなくてもデフォルト値が使われる */}
<Profile name="田中花子" age={30} />
{/* bio を渡せば上書きされる */}
<Profile name="鈴木一郎" age={28} bio="フルスタックエンジニアです" />
</div>
)
}
デフォルト値は「そのPropsが undefined のとき」に適用されます。null を渡すとデフォルト値は使われません。これはJavaScriptの分割代入の仕様です。
Propsは読み取り専用
重要なルールがあります:Propsは読み取り専用です。受け取ったPropsを変更してはいけません。
// 絶対にやってはいけない
function Profile({ name }) {
name = name.toUpperCase() // NG!Props を書き換えている
return <h2>{name}</h2>
}
// 正しい書き方
function Profile({ name }) {
const displayName = name.toUpperCase() // 新しい変数に入れる
return <h2>{displayName}</h2>
}
Propsを変更しようとするとReactが警告を出します。コンポーネントは受け取ったデータを表示するだけで、データの変更は親コンポーネントの責任です(これを「一方向データフロー」と呼びます)。
children プロップ
特別なPropとして children があります。コンポーネントのタグで囲んだ内側のJSXが children として渡されます。
// children を受け取るコンポーネント
function Card({ title, children }) {
return (
<div style={{
border: '1px solid #ddd',
borderRadius: '8px',
padding: '1.5rem',
marginBottom: '1rem'
}}>
<h3 style={{ marginTop: 0 }}>{title}</h3>
{/* ここに子要素がレンダリングされる */}
{children}
</div>
)
}
// 使う側:タグで囲んだ内容が children になる
function App() {
return (
<div>
<Card title="React学習メモ">
<p>コンポーネントは再利用可能なUI部品です</p>
<p>Propsで外からデータを渡せます</p>
</Card>
<Card title="今日のタスク">
<ul>
<li>Propsを理解する</li>
<li>演習問題を解く</li>
</ul>
</Card>
</div>
)
}
children を使うとレイアウトやラッパーコンポーネントを作れます。「外枠のデザインは固定で、中身は自由に変えたい」という場面で非常に便利です。
children はどんなJSXでも受け取れます。テキスト、要素、コンポーネント、配列など何でもOKです。Laravelのbladeでいえば @yield('content') に相当します。
実践:汎用的なコンポーネントを作る
ここで前のチャプターの Profile コンポーネントをPropsを使って汎用化しましょう。
まず、Propsで受け取れるように Profile.jsx を書き直します:
// src/components/Profile.jsx
function Profile({ name, bio, imageUrl, hobbies = [] }) {
return (
<section style={{
display: 'flex',
gap: '1.5rem',
padding: '1.5rem',
borderBottom: '1px solid #eee'
}}>
{/* 画像が渡された場合のみ表示 */}
{imageUrl && (
<img
src={imageUrl}
alt={`${name}のプロフィール写真`}
style={{
width: '80px',
height: '80px',
borderRadius: '50%',
objectFit: 'cover',
flexShrink: 0
}}
/>
)}
<div>
<h2 style={{ margin: '0 0 0.5rem' }}>{name}</h2>
<p style={{ margin: '0 0 0.75rem', color: '#666' }}>{bio}</p>
{hobbies.length > 0 && (
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
{hobbies.map((hobby) => (
<span key={hobby} style={{
background: '#eff6ff',
color: '#2563eb',
padding: '0.2rem 0.75rem',
borderRadius: '999px',
fontSize: '0.875rem'
}}>
{hobby}
</span>
))}
</div>
)}
</div>
</section>
)
}
export default Profile
App.jsx で同じコンポーネントを使って複数人のプロフィールを表示します:
// src/App.jsx
import Header from './components/Header'
import Profile from './components/Profile'
import Footer from './components/Footer'
// プロフィールデータを配列で定義
const members = [
{
id: 1,
name: '山田太郎',
bio: 'バックエンドエンジニア。Laravelが得意です。',
imageUrl: 'https://i.pravatar.cc/150?img=1',
hobbies: ['Laravel', 'MySQL', 'コーヒー'],
},
{
id: 2,
name: '田中花子',
bio: 'フロントエンドエンジニア。ReactとTypeScriptを使っています。',
imageUrl: 'https://i.pravatar.cc/150?img=5',
hobbies: ['React', 'TypeScript', 'デザイン'],
},
{
id: 3,
name: '鈴木一郎',
bio: 'インフラエンジニア。最近フロントも勉強中です。',
imageUrl: 'https://i.pravatar.cc/150?img=8',
hobbies: ['Docker', 'Kubernetes', 'Go'],
},
]
function App() {
return (
<>
<Header />
<main style={{ maxWidth: '700px', margin: '0 auto' }}>
{/* 配列から map でコンポーネントをレンダリング */}
{members.map((member) => (
<Profile
key={member.id}
name={member.name}
bio={member.bio}
imageUrl={member.imageUrl}
hobbies={member.hobbies}
/>
))}
</main>
<Footer />
</>
)
}
export default App
members.map((member) => <Profile key={member.id} {...member} />) のようにスプレッド演算子 {...member} を使うと、オブジェクトのプロパティをまとめてPropsとして渡せます。コードが短くなりますが、どのPropsが渡っているか分かりにくくなるため、チームの方針に合わせて使いましょう。
上のコードを実装して、3人のプロフィールが表示されることを確認しましょう。
さらに余裕があれば以下に挑戦してみてください:
発展課題 1: Card ラッパーコンポーネントを作る
children プロップを使った Card コンポーネントを作り、Profile を Card で囲んでみましょう。
// src/components/Card.jsx
function Card({ children }) {
return (
<div style={{
border: '1px solid #e2e8f0',
borderRadius: '12px',
marginBottom: '1rem',
overflow: 'hidden',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
}}>
{children}
</div>
)
}
export default Card// App.jsx での使い方
{members.map((member) => (
<Card key={member.id}>
<Profile
name={member.name}
bio={member.bio}
imageUrl={member.imageUrl}
hobbies={member.hobbies}
/>
</Card>
))}発展課題 2: Propsにデフォルト値を追加する
Profile コンポーネントで imageUrl が渡されなかった場合に、デフォルトのアバター画像(プレースホルダー)を表示してみましょう。
function Profile({ name, bio, imageUrl = 'https://i.pravatar.cc/150?img=0', hobbies = [] }) {
// ...
} 完成したらこんな画面になります:
Propsのよくある間違い
最後に、Propsでよく起きるミスをまとめておきます。
// よくある間違い1:数値を文字列として渡す
<Profile age="25" /> // age は数値なのに文字列になってしまう
<Profile age={25} /> // 正しい
// よくある間違い2:Props名のタイポ
function Profile({ naem }) { ... } // 'name' のタイポ
<Profile name="山田" /> // naem は undefined になる
// よくある間違い3:Propsを直接変更する
function Profile({ user }) {
user.name = '変更後' // NG!
}
TypeScriptを使うとこういったミスをエディタが検出してくれます。TypeScriptへの移行は少し先のチャプターで扱います。
まとめ
Propsのポイントをまとめます:
- Propsはコンポーネントに外からデータを渡す仕組み
- 文字列は
""で、それ以外は{}で渡す - 受け取り側では分割代入
{ name, age }を使うのが一般的 - デフォルト値は
{ bio = 'デフォルト値' }で設定できる childrenプロップでタグに囲まれた内側のJSXを受け取れる- Propsは読み取り専用。変更してはいけない
これでコンポーネントを汎用的に使い回せるようになりました。でもまだ問題があります。ユーザーがボタンをクリックしたり入力したりしたとき、コンポーネントの表示を動的に変えたいですよね。
次のチャプターでは State(状態) を学びます。コンポーネントが「自分のデータ」を持って、それに応じて表示を更新できるようになります。