関数コンポーネントとプロップ
関数コンポーネントはレゴブロックのようなものです。それぞれのブロックには独自の形状(プロップ)があり、さまざまな方法で組み立てたり組み合わせたりすることができます。プロップはブロックの「インターフェース」のようなものです。ブロックにどのような色やサイズを設定しても、その通りに表示されます。
1. 学習内容
- 関数コンポーネントの定義と命名規則
- 小道具の受け渡し
- プロパティの種類(文字列 / 数値 / ブール値 / オブジェクト)
- 子供向け特別小道具
- TypeScript を使用してプロパティの型を注釈付ける
2. あるフロントエンドチームの実話
コンポーネントとプロップ間のデータフロー
flowchart TD
A[Parent Component] -->|"name='Alice'"| B[UserCard]
A -->|"items=[...]"| C[ProductList]
C -->|"item={...}"| D[ProductCard]
B -->|"children"| E[<Avatar />]
A -->|"onFollow callback"| B
style A fill:#e3f2fd,stroke:#1565c0
style B fill:#e8f5e9,stroke:#2e7d32
style C fill:#fff3e0,stroke:#e65100
style D fill:#fce4ec,stroke:#c62828
(1) 課題:50回もコピー&ペーストしなければならないボタン
チャーリーはフロントエンドチームのテクニカルリードです。彼は、コードリポジトリ内の20ページにわたり、50種類以上の異なるボタンが散在していることを発見しました:
// Page A:Red Button
<button style={{ backgroundColor: 'red', color: 'white' }}>
Delete
</button>
// Page B:Blue Button
<button style={{ backgroundColor: 'blue', color: 'white' }}>
Submit
</button>
// Page C:Small gray button
<button style={{ backgroundColor: 'gray', color: 'black', fontSize: '12px' }}>
Cancel
</button>
質問:
- デザインの変更:すべてのボタンを丸みを帯びた形状にする必要があります。これに伴い、50箇所で変更が必要です。
- 不統一:ボタンによっては
redが使われているものもあれば、#ff0000が使われているもの、rgb(255,0,0)が使われているものもある - よくある不具合:スタイルの設定が間違っていたため、ボタン全体が透明になってしまった
(2) コンポーネントベースのアプローチ
関数コンポーネントとプロップを用いた統一的な管理:
function Button({ variant = 'primary', size = 'medium', children }) {
const styles = {
primary: { backgroundColor: '#1890ff', color: 'white' },
danger: { backgroundColor: '#ff4d4f', color: 'white' },
default: { backgroundColor: '#f0f0f0', color: '#333' }
}
return (
<button style={{
...styles[variant],
padding: size === 'large' ? '12px 24px' : '8px 16px',
border: 'none',
borderRadius: '6px',
cursor: 'pointer'
}}>
{children}
</button>
)
}
// Usage:Done in one line of code
<Button variant="danger">Delete</Button>
<Button variant="primary">Submit</Button>
<Button variant="default">Cancel</Button>
メリット:ボタンの数が50個のフラグメントから1つのコンポーネントに削減されました。これにより、デザインを変更する際には1か所のみを修正すればよくなり、スタイルの一貫性が確保されます。
3. 関数コンポーネントの基礎
(1) コンポーネントの定義
Reactにおいて、関数コンポーネントとは、JSXを返すJavaScript関数のことです。
// The Simplest Function Component
function Welcome() {
return <h1>Hello, React!</h1>
}
// How to Write Arrow Functions(More common)
const Welcome = () => {
return <h1>Hello, React!</h1>
}
// Implicit Return(Only one line JSX "when " can be omitted return)
const Welcome = () => <h1>Hello, React!</h1>
| ルール | 説明 |
|---|---|
| 最初の文字を大文字にする | Welcome ✅ welcome ❌ (ReactはコンポーネントとHTMLタグを区別します) |
| JSXを返す | JSX要素(またはnull)を返さなければならない |
| 純粋関数 | 同じ入力(Props)に対しては、同じ出力(UI)を返さなければならない |
| プロパティは変更できません | プロパティは読み取り専用であり、コンポーネント内では変更できません |
(2) プロップスは関数の引数である
プロップス(「Properties」の略)とは、親コンポーネントから子コンポーネントに渡される読み取り専用データのことです。
// ---- Define a Component ----
function Greeting(props) {
// props It is an object,Includes all incoming parameters
return <h1>Hello,{props.name}!This year, you {props.age} years old。</h1>
}
// ---- Using Components ----
<Greeting name="Alice" age={28} />
// Rendering Results:
// <h1>Hello,Alice!This year, you 28 years old。</h1>
(3) プロパティのデストラクチャリング構文
より一般的なアプローチは、目的のパラメータを直接取得する分解的代入です:
// Deconstructive Writing Style(Recommendations)
function Greeting({ name, age }) {
return <h1>Hello,{name}!This year, you {age} years old。</h1>
}
// equivalent to:
function Greeting(props) {
const { name, age } = props
return <h1>Hello,{name}!This year, you {age} years old。</h1>
}
4. 小道具の種類
| プロパティの種類 | 指定構文 | 取得例 | 備考 |
|---|---|---|---|
| 文字列 | name="Alice" |
{ name } → "Alice" |
二重引用符で囲まれている |
| 番号 | age={30} |
{ age } → 30 |
{} を使用する必要があります |
| ブール値 | active / active={true} |
{ active } → true |
値を省略した場合はデフォルトで true となる |
| オブジェクト | info={{ key: 'val' }} |
{ info } → { key: 'val' } |
二重中括弧 |
| 配列 | items={[1, 2, 3]} |
{ items } → [1,2,3] |
{} を使用する必要があります |
| 関数 | onClick={handler} |
{ onClick } → fn |
コールバック/イベントハンドラ |
| コンポーネント | icon={<Icon />} |
{ icon } → JSX |
動的にレンダリングされるコンポーネント |
プロップスには、文字列、数値、ブール値、オブジェクト、配列、関数、さらには他のコンポーネントなど、あらゆるJavaScriptのデータ型を渡すことができます。
(1) 文字列
文字列は、二重引用符を使って直接渡すことができます(HTMLの属性と同じように):
function UserCard({ name, role }) {
return (
<div className="card">
<h2>{name}</h2>
<p>Characters:{role}</p>
</div>
)
}
// Strings are enclosed in double quotes,For other types {} Package
<UserCard name="Alice" role="Administrator" />
(2) 数値、ブール値、およびオブジェクト
文字列以外の型は、{}で囲む必要があります:
function Product({ name, price, inStock, details }) {
return (
<div className="product">
<h3>{name}</h3>
<p>Price:${price}</p>
{/* Boolean values are used for conditional rendering */}
{inStock ? <span>In stock ✅</span> : <span>Out of stock ❌</span>}
{/* Object:Accessible properties */}
<p>Category:{details.category} | Weight:{details.weight}g</p>
</div>
)
}
// Using Components
<Product
name="Wireless Mouse"
price={89}
inStock={true}
details={{ category: 'Peripherals', weight: 120 }}
/>
(3) 値渡しメソッドのクイックリファレンス表
| 種類 | 例 | 説明 |
|---|---|---|
| 文字列 | name="Alice" |
HTML属性と同じ書き方 |
| 番号 | count={42} |
{} が必要 |
| ブール値 true | active または active={true} |
プロパティ名のみが指定された場合、デフォルトは true となります |
| ブール値 false | active={false} |
{} が必要 |
| オブジェクト | data={{ x:1, y:2 }} |
二重中括弧 |
| 配列 | items={[1,2,3]} |
{} が必要 |
| 式 | result={a + b} |
任意の JavaScript 式 |
| 変数 | name={userName} |
値渡し変数 |
(1) ▶ サンプル:ユーザー情報カード
// ============================================
// Example:use Props Build a User Information Card
// Features:Display Props Transmission of Various Types
// ============================================
function UserCard({ name, age, isOnline, avatar, tags }) {
return (
<div style={{
border: '1px solid #ddd',
borderRadius: '8px',
padding: '20px',
maxWidth: '300px'
}}>
<img
src={avatar}
alt={name}
style={{ width: '80px', height: '80px', borderRadius: '50%' }}
/>
<h2>{name}</h2>
<p>Age:{age} years old</p>
<p>
Status:
<span style={{ color: isOnline ? '#52c41a' : '#999' }}>
{isOnline ? 'Online' : 'Offline'}
</span>
</p>
<div>
Tags:
{tags.map(tag => (
<span key={tag} style={{
background: '#f0f0f0',
padding: '2px 8px',
borderRadius: '4px',
margin: '0 4px',
fontSize: '12px'
}}>
{tag}
</span>
))}
</div>
</div>
)
}
// Using Components
<UserCard
name="Alice"
age={28}
isOnline={true}
avatar="https://i.pravatar.cc/80"
tags={['Front End', 'React', 'TypeScript']}
/>
5. 子要素とデフォルト値
(1) children:コンポーネント内にネストされたコンテンツ
children は、コンポーネントタグで囲まれた内容を表す特別なプロップです。
// Definition:use children Content between tags
function Card({ children, title }) {
return (
<div style={{ border: '1px solid #ddd', padding: '16px', borderRadius: '8px' }}>
<h3>{title}</h3>
<div>{children}</div> {/* The content between the tags is displayed here */}
</div>
)
}
// Usage:The content between the tags automatically becomes children
<Card title="Announcement">
<p>The system will go live tonight 22:00 Perform maintenance,Expected to continue 2 hours。</p>
<p>Please save your work in advance.,To prevent data loss。</p>
</Card>
// Rendering Results:
// <div class="card">
// <h3>Announcement</h3>
// <div>
// <p>The system will go live tonight 22:00 Perform maintenance...</p>
// <p>Please save your work in advance....</p>
// </div>
// </div>
(2) デフォルト値
ES6のデストラクチャリング構文とデフォルト値を使用して、propsのデフォルト値を設定します:
function Button({ text = 'Click', color = 'blue', size = 'medium' }) {
return (
<button style={{
backgroundColor: color,
padding: size === 'large' ? '12px 24px' : '8px 16px',
color: 'white',
border: 'none',
borderRadius: '4px'
}}>
{text}
</button>
)
}
// No parameters → Use the default values
<Button /> // Blue、medium、"Click"
// Pass some parameters → Override the default value
<Button text="Submit" /> // Blue、medium、"Submit"
<Button color="red" size="large" /> // Red、large、"Click"
6. TypeScriptの型注釈(概要)
TypeScript を使用すると、props の厳密な型を定義でき、これによりエディタが自動補完の候補を表示したり、型チェックを実行したりできるようになります:
// ---- Definition Props Type ----
interface ButtonProps {
text: string // Required:string
color?: string // Optional: add ? suffix
size?: 'small' | 'medium' | 'large' // Optional: limited to 3 values
onClick: () => void // Required:Function Types
disabled?: boolean // Optional: boolean, defaults to false
}
// ---- Using in a component ----
function Button({ text, color = 'blue', size = 'medium', onClick, disabled = false }: ButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
style={{
backgroundColor: color,
padding: size === 'large' ? '12px 24px' : '8px 16px',
opacity: disabled ? 0.5 : 1,
color: 'white',
border: 'none',
borderRadius: '4px'
}}
>
{text}
</button>
)
}
// Usage:The editor provides smart suggestions
<Button
text="Save"
color="#1890ff"
size="medium"
onClick={() => alert('Saved successfully!')}
/>
// ❌ Error:TypeScript It will throw an error
<Button text="Test" /> // Required field is missing onClick
<Button text="Test" size="xl" onClick={fn} /> // size Outside the permitted range
7. 完全な例:商品カードコンポーネント
// ============================================
// Example:Product Card Component(Comprehensive Application Props Various Types)
// ============================================
// Type Definitions
interface Product {
id: number
name: string
price: number
image: string
category: string
tags: string[]
}
interface ProductCardProps {
product: Product
onAddToCart: (productId: number) => void
showDiscount?: boolean
size?: 'small' | 'large'
}
// Component Implementation
function ProductCard({
product,
onAddToCart,
showDiscount = false,
size = 'large'
}: ProductCardProps) {
return (
<div style={{
border: '1px solid #eee',
borderRadius: '8px',
padding: size === 'large' ? '16px' : '10px',
width: size === 'large' ? '280px' : '200px'
}}>
<img
src={product.image}
alt={product.name}
style={{ width: '100%', height: '150px', objectFit: 'cover' }}
/>
<h3>{product.name}</h3>
<p style={{ color: '#ff4d4f', fontSize: '20px', fontWeight: 'bold' }}>
${product.price}
{showDiscount && <span style={{ fontSize: '12px', color: '#999', textDecoration: 'line-through', marginLeft: '8px' }}>${(product.price * 1.2).toFixed(0)}</span>}
</p>
<p style={{ color: '#666', fontSize: '14px' }}>{product.category}</p>
<div>
{product.tags.map(tag => (
<span key={tag} style={{ background: '#f0f5ff', color: '#1890ff', padding: '2px 6px', borderRadius: '4px', margin: '0 2px', fontSize: '12px' }}>
{tag}
</span>
))}
</div>
<button
onClick={() => onAddToCart(product.id)}
style={{
width: '100%',
padding: '8px',
marginTop: '10px',
backgroundColor: '#1890ff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Add to Cart
</button>
</div>
)
}
// Using Components
const myProduct = {
id: 101,
name: 'Mechanical Keyboard',
price: 299,
image: 'https://via.placeholder.com/280x150',
category: 'Computer Peripherals',
tags: ['Hot Seller', 'New Products']
}
<ProductCard
product={myProduct}
onAddToCart={(id) => console.log('Add to Cart:', id)}
showDiscount={true}
/>
期待される出力:キーボードの画像、商品名「Mechanical Keyboard」、価格$299(元価格$359を示す取り消し線付き)、カテゴリ「Computer Peripherals」、タグ「Hot Seller」および「New Arrival」が表示された商品カード。その下には青い「Add to Cart」ボタンがある。
(1) ▶ サンプル:プロップスの型渡しとデフォルト値の総合的な活用
// ============================================
// Example:Profile Card——Props Combined Use of Types and Default Values
// Features:Display string、number、boolean、object、function etc. Props Type Passing
// ============================================
function ProfileCard({
name = 'Anonymous User',
age = 0,
isActive = false,
avatar = 'https://via.placeholder.com/60',
skills = [],
onGreet
}) {
return (
<div style={{
border: '1px solid #e8e8e8',
borderRadius: '12px',
padding: '20px',
maxWidth: '320px',
boxShadow: '0 2px 8px rgba(0,0,0,0.06)'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px', marginBottom: '16px' }}>
<img
src={avatar}
alt={name}
style={{ width: '60px', height: '60px', borderRadius: '50%', objectFit: 'cover' }}
/>
<div>
<h3 style={{ margin: 0 }}>{name}</h3>
<p style={{ margin: '4px 0 0 0', color: '#666', fontSize: '14px' }}>
{age} years old · {isActive ? '🟢 Online' : '🔴 Offline'}
</p>
</div>
</div>
{skills.length > 0 && (
<div style={{ marginBottom: '12px' }}>
<p style={{ margin: '0 0 8px 0', fontWeight: 'bold', fontSize: '14px' }}>Skills:</p>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
{skills.map((skill, index) => (
<span key={index} style={{
backgroundColor: '#f0f5ff',
color: '#1890ff',
padding: '4px 10px',
borderRadius: '12px',
fontSize: '12px'
}}>
{skill}
</span>
))}
</div>
</div>
)}
{onGreet && (
<button
onClick={() => onGreet(name)}
style={{
width: '100%',
padding: '8px',
backgroundColor: '#1890ff',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '14px'
}}
>
Say hello to {name}
</button>
)}
</div>
)
}
// Usage:Pass all parameters
<ProfileCard
name="Alice"
age={28}
isActive={true}
avatar="https://i.pravatar.cc/60"
skills={['React', 'TypeScript', 'Node.js']}
onGreet={(name) => alert(`Hello,${name}!`)}
/>
// Usage:Pass only some parameters(Use the default values for the rest)
<ProfileCard
name="Bob"
skills={['Vue', 'Python']}
/>
// Usage:Do not pass any parameters(Use all default values)
<ProfileCard />
(2) ▶ サンプル:イベントコールバックのプロパティ — 子コンポーネントから親コンポーネントへの通知
function AddToCartButton({ productName, onAdd }) {
const [adding, setAdding] = useState(false)
async function handleClick() {
setAdding(true)
await onAdd(productName)
setAdding(false)
}
return (
<button onClick={handleClick} disabled={adding}
style={{ padding: '8px 16px', cursor: adding ? 'wait' : 'pointer' }}>
{adding ? 'Adding...' : `Add ${productName}`}
</button>
)
}
function ProductPage() {
const [cart, setCart] = useState([])
function handleAdd(name) {
setCart(prev => [...prev, name])
}
return (
<div>
<h2>Products</h2>
<AddToCartButton productName="Keyboard" onAdd={handleAdd} />
<AddToCartButton productName="Mouse" onAdd={handleAdd} />
<p>Cart: {cart.join(', ') || 'empty'}</p>
</div>
)
}
(3) ▶ サンプル:プロパティの条件付きレンダリング — コンポーネントの動的な切り替え
function StatusMessage({ status }) {
const config = {
loading: { icon: '⏳', color: '#1890ff', text: 'Loading...' },
success: { icon: '✅', color: '#52c41a', text: 'Success!' },
error: { icon: '❌', color: '#ff4d4f', text: 'Error occurred' },
empty: { icon: '📭', color: '#999', text: 'No data' },
}
const { icon, color, text } = config[status] || config.empty
return (
<div style={{ padding: 16, textAlign: 'center', color }}>
<span style={{ fontSize: 32 }}>{icon}</span>
<p>{text}</p>
</div>
)
}
function DataViewer() {
const [status, setStatus] = useState('loading')
return (
<div style={{ maxWidth: 300, margin: '0 auto' }}>
<StatusMessage status={status} />
<div style={{ display: 'flex', gap: 4 }}>
{['loading', 'success', 'error', 'empty'].map(s => (
<button key={s} onClick={() => setStatus(s)}
style={{ padding: '4px 8px', cursor: 'pointer', fontSize: 12 }}>
{s}
</button>
))}
</div>
</div>
)
}
(4) ▶ サンプル:renderProps パターン — リストコンポーネントとカスタムレンダリング
function DataList({ items, renderItem, keyExtractor, emptyMessage = 'No items' }) {
if (!items || items.length === 0) {
return <p style={{ color: '#999', textAlign: 'center', padding: 20 }}>{emptyMessage}</p>
}
return (
<ul style={{ listStyle: 'none', padding: 0 }}>
{items.map(item => (
<li key={keyExtractor(item)} style={{ borderBottom: '1px solid #f0f0f0', padding: 8 }}>
{renderItem(item)}
</li>
))}
</ul>
)
}
function App() {
const users = [
{ id: 1, name: 'Alice', email: 'alice@example.com', active: true },
{ id: 2, name: 'Bob', email: 'bob@example.com', active: false },
{ id: 3, name: 'Charlie', email: 'charlie@example.com', active: true },
]
const products = [
{ id: 'a', name: 'Keyboard', price: 79 },
{ id: 'b', name: 'Monitor', price: 399 },
]
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<h3>Users</h3>
<DataList
items={users}
keyExtractor={u => u.id}
renderItem={u => (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{u.name} ({u.email})</span>
<span style={{ color: u.active ? '#52c41a' : '#999' }}>
{u.active ? 'Active' : 'Inactive'}
</span>
</div>
)}
/>
<h3>Products</h3>
<DataList
items={products}
keyExtractor={p => p.id}
emptyMessage="No products available"
renderItem={p => (
<span>{p.name} - ${p.price}</span>
)}
/>
</div>
)
}
❓ よくある質問
<div>はHTMLのdivタグであり、<Div>はカスタムコンポーネントです。コンポーネント名を小文字で記述すると、ReactはそれをHTMLタグとして扱ってしまうため、レンダリングに失敗します。function Card({ title = 'Untitled' }) {} 時にデフォルト値を割り当てる;② Card.defaultProps = { title: 'Untitled' } を使用する。なお、Reactでは(関数コンポーネント内での)defaultProps の使用はもはや推奨されていません。デフォルト値の割り当てにはデストラクチャリングを使用することが推奨されています。TypeScriptでは、title?: string を使用してプロップがオプションであることを示すこともできます。これにより、デフォルト値を割り当てるためのデストラクチャリングと組み合わせることで、コードがより明確になります。📖 まとめ
- 関数コンポーネントとは、JSXを返すJavaScript関数のことです。最初の文字は大文字でなければならない
- プロップは、親コンポーネントから子コンポーネントに渡される読み取り専用データであり、子コンポーネントはこれを変更することはできません。
- プロップスは、文字列、数値、ブール値、オブジェクト、配列、関数など、どのような型でも指定できます。
childrenは、コンポーネントタグの間に含まれるコンテンツを表す特別なプロップです- デストラクチャリング構文
function Comp({ prop1, prop2 })を使って、コードをより分かりやすくしましょう - TypeScriptの型注釈は、開発体験とコードの堅牢性を大幅に向上させることができます
📝 練習問題
- 基本問題(難易度 ⭐):2つのプロップ(
name(文字列)とsize(数値、デフォルトは50))を受け取り、ユーザーのアバターと名前を表示するAvatarコンポーネントを作成してください。 - 上級問題(難易度 ⭐⭐):2つのプロップ——
tags(文字列の配列)とonRemove(関数)——を受け取り、タグのリストをレンダリングするTagListコンポーネントを作成してください。タグをクリックすると、そのタグが削除されます。 - 課題(難易度 ⭐⭐⭐):TypeScriptで、
DataTableコンポーネントを作成してください。このコンポーネントは、columns(列定義の配列)とdata(データの配列)を受け取り、シンプルなデータテーブルをレンダリングするものです。ゼブラパターンを制御するためのstriped(ブール値、オプション)プロパティを追加してみてください。



