404 Not Found

404 Not Found


nginx

関数コンポーネントとプロップ

関数コンポーネントはレゴブロックのようなものです。それぞれのブロックには独自の形状(プロップ)があり、さまざまな方法で組み立てたり組み合わせたりすることができます。プロップはブロックの「インターフェース」のようなものです。ブロックにどのような色やサイズを設定しても、その通りに表示されます。


1. 学習内容



2. あるフロントエンドチームの実話

コンポーネントとプロップ間のデータフロー

100%
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種類以上の異なるボタンが散在していることを発見しました:

JSX
// 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>

質問:

(2) コンポーネントベースのアプローチ

関数コンポーネントとプロップを用いた統一的な管理:

JSX
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関数のことです。

JSX
// 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>
ルール 説明
最初の文字を大文字にする Welcomewelcome ❌ (ReactはコンポーネントとHTMLタグを区別します)
JSXを返す JSX要素(またはnull)を返さなければならない
純粋関数 同じ入力(Props)に対しては、同じ出力(UI)を返さなければならない
プロパティは変更できません プロパティは読み取り専用であり、コンポーネント内では変更できません

(2) プロップスは関数の引数である

プロップス(「Properties」の略)とは、親コンポーネントから子コンポーネントに渡される読み取り専用データのことです。

JSX
// ---- 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) プロパティのデストラクチャリング構文

より一般的なアプローチは、目的のパラメータを直接取得する分解的代入です:

JSX
// 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の属性と同じように):

JSX
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) 数値、ブール値、およびオブジェクト

文字列以外の型は、{}で囲む必要があります:

JSX
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) ▶ サンプル:ユーザー情報カード

JSX
// ============================================
// 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 は、コンポーネントタグで囲まれた内容を表す特別なプロップです。

JSX
// 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のデフォルト値を設定します:

JSX
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 の厳密な型を定義でき、これによりエディタが自動補完の候補を表示したり、型チェックを実行したりできるようになります:

TSX
// ---- 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. 完全な例:商品カードコンポーネント

TSX
// ============================================
// 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) ▶ サンプル:プロップスの型渡しとデフォルト値の総合的な活用

JSX
// ============================================
// 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) ▶ サンプル:イベントコールバックのプロパティ — 子コンポーネントから親コンポーネントへの通知

JSX
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) ▶ サンプル:プロパティの条件付きレンダリング — コンポーネントの動的な切り替え

JSX
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 パターン — リストコンポーネントとカスタムレンダリング

JSX
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>
  )
}
▶ 試してみよう

❓ よくある質問

Q なぜプロップは読み取り専用なのですか?直接変更することはできないのですか?
A Reactの設計思想は「一方向のデータフロー」です。データは親コンポーネントから子コンポーネントへと流れ、子コンポーネントは親のデータを変更することはできません。子コンポーネントがデータを変更する必要がある場合は、プロップスを介して渡されたコールバック関数を通じて親コンポーネントに通知する必要があります(「単一の真実の源」を維持するため)。これにより、明確なデータフローが確保され、デバッグやメンテナンスが容易になります。
Q propsとstateの違いは何ですか?
A propsは外部から渡される読み取り専用のデータであり、stateはコンポーネント内部で管理される変更可能なデータです。propsはコンポーネントの「設定パラメータ」、stateは「内部状態」と考えると分かりやすいでしょう。propsが変更されるとコンポーネントは再レンダリングされ、stateが変更されても同様にコンポーネントは再レンダリングされます。
Q コンポーネント名はすべて大文字でなければならないのでしょうか?小文字で記述した場合はどうなりますか?
A すべて大文字で記述する必要があります。Reactは、大文字と小文字の区別によってコンポーネントとHTMLタグを区別します。<div>はHTMLのdivタグであり、<Div>はカスタムコンポーネントです。コンポーネント名を小文字で記述すると、ReactはそれをHTMLタグとして扱ってしまうため、レンダリングに失敗します。
Q コンポーネントはどの程度までネストできますか?制限はありますか?
A 技術的な制限はありませんが、3~4レベルを超えないことをお勧めします。ネストが深すぎると、データの受け渡し(プロップスのドリルダウン)が複雑になり、コードの可読性が低下する可能性があります。複数のレベルにわたってデータを渡す必要がある場合は、Context API(第13課)やグローバル状態管理(第18課)の利用を検討してください。
Q プロパティのデフォルト値を設定するにはどうすればよいですか?
A 2つの方法があります:① デストラクチャリング function Card({ title = 'Untitled' }) {} 時にデフォルト値を割り当てる;② Card.defaultProps = { title: 'Untitled' } を使用する。なお、Reactでは(関数コンポーネント内での)defaultProps の使用はもはや推奨されていません。デフォルト値の割り当てにはデストラクチャリングを使用することが推奨されています。TypeScriptでは、title?: string を使用してプロップがオプションであることを示すこともできます。これにより、デフォルト値を割り当てるためのデストラクチャリングと組み合わせることで、コードがより明確になります。

📖 まとめ


📝 練習問題

  1. 基本問題(難易度 ⭐):2つのプロップ(name(文字列)とsize(数値、デフォルトは50))を受け取り、ユーザーのアバターと名前を表示するAvatarコンポーネントを作成してください。
  2. 上級問題(難易度 ⭐⭐):2つのプロップ——tags(文字列の配列)とonRemove(関数)——を受け取り、タグのリストをレンダリングするTagListコンポーネントを作成してください。タグをクリックすると、そのタグが削除されます。
  3. 課題(難易度 ⭐⭐⭐):TypeScriptで、DataTableコンポーネントを作成してください。このコンポーネントは、columns(列定義の配列)とdata(データの配列)を受け取り、シンプルなデータテーブルをレンダリングするものです。ゼブラパターンを制御するための striped(ブール値、オプション)プロパティを追加してみてください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%