404 Not Found

404 Not Found


nginx

条件付きレンダリングとリストレンダリング

条件付きレンダリングは信号機のようなもので、状態に基づいて何を表示するかを決定します。一方、リストレンダリングは流れ作業のようなもので、データのリストから同一のUIコンポーネントをバッチ処理で生成します。


1. 学習内容


2. 電子商取引プラットフォームのフロントエンド事情

(1) 課題:商品リストの6つの状態

アリスは、あるECプラットフォームの商品一覧ページの制作に取り組んでいます。このページには6つの表示状態があり、状態が変わるたびに、彼女は手動でDOMを操作しなければなりません:

JAVASCRIPT
function renderProductList(state) {
  const container = document.getElementById('product-list')
  container.innerHTML = ''

  if (state.isLoading) {
    container.innerHTML = '<div class="loading">Loading......</div>'
  } else if (state.error) {
    container.innerHTML = '<div class="error">Failed to load</div>'
  } else if (!state.isLoggedIn) {
    container.innerHTML = '<div>Please log in</div>'
  } else if (state.products.length === 0) {
    container.innerHTML = '<div>No products available</div>'
  } else {
    let html = ''
    state.products.forEach(p => {
      html += `<div class="card"><h3>${p.name}</h3><p>$${p.price}</p></div>`
    })
    container.innerHTML = html
  }
}

アリスの質問:状態が変わるたびに、手動でコンテナをクリアしてHTMLを生成しなければなりません。たった30行のコードで、現在の状態を表示するというたった一つのことしかできていません。

(2) Reactにおける条件付きレンダリングの解決策

JSX
function ProductList({ user, products, isLoading, error }) {
  // Each state is independent if Branch,No interference with one another
  if (isLoading) return <LoadingSpinner />
  if (error) return <ErrorMessage message={error} />
  if (!user) return <LoginPrompt />
  if (products.length === 0) return <EmptyState />
  
  return (
    <div className="product-grid">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  )
}

メリット:わずか15行のコードですべての状態を網羅でき、各分岐が一目で理解しやすい。



3. 条件付きレンダリングの3つの活用方法

メソッド 構文 使用例 備考
三項演算子 condition ? A : B 2つの選択肢から1つを選ぶ(「else」分岐を含む) 三項演算子のネストは可読性を損なうため、2レベル以上は避ける
論理演算と && condition && A 条件が満たされた場合にのみ表示(「else」なし) 0 && <Comp /> は 0 を返すため、> 0 を使用してください
論理OR ` ` `value

(1) 三項演算子 ? :

「2つの選択肢から1つを選ぶ」という状況に適したシナリオ:

JSX
function StatusBadge({ isActive }) {
  return (
    <span style={{ color: isActive ? '#52c41a' : '#ff4d4f' }}>
      {isActive ? '🟢 Online' : '🔴 Offline'}
    </span>
  )
}

// isActive=true  → <span>🟢 Online</span>
// isActive=false → <span>🔴 Offline</span>

(2) 論理と &&

「条件が満たされた場合にのみ表示し、そうでない場合は表示しない」という用途に適しています:

JSX
function Notification({ count }) {
  return (
    <div className="notification-icon">
      🔔
      {/* count > 0 A red dot appears,Otherwise, nothing will be displayed. */}
      {count > 0 && (
        <span className="badge">{count > 99 ? '99+' : count}</span>
      )}
    </div>
  )
}

// count=0  → Show only 🔔
// count=5  → Show 🔔 and  "5"
⚠️ 注意点{items.length && <List />} と記述しないでください。items.length が 0 の場合、0 && <List /> の結果は 0 となり、React はページ上に 0 をレンダリングしてしまいます。正しい記述方法は {items.length > 0 && <List />} です。

(3) 論理OR ||

「フォールバック値の提供」に適しています:

JSX
function UserProfile({ user }) {
  return (
    <div>
      <h2>{user.nickname || 'Anonymous User'}</h2>
      <p>{user.bio || 'This person is lazy.,Nothing was written...'}</p>
      <p>Location:{user.location || 'Unknown'}</p>
    </div>
  )
}

(1) ▶ サンプル:種類の条件付きレンダリングの並列比較

JSX
// ============================================
// Example:Combined Use of Three Types of Conditional Rendering in the User Panel
// ============================================

function UserPanel({ user }) {
  return (
    <div className="panel">
      {/* 1. Ternary:Choose one of the two */}
      <h2>
        {user.isVIP ? '⭐ Premium Member' : 'Regular User'}
      </h2>

      {/* 2. &&:Display only when met */}
      {user.isVIP && (
        <p>Membership Expiration:{user.vipExpireDate}</p>
      )}

      {/* 3. ||:Floor value */}
      <p>Signature:{user.slogan || 'This person is really cool.,No signature'}</p>

      {/* Combined Use:List of Tags */}
      {user.tags && user.tags.length > 0 && (
        <div>
          {user.tags.map(tag => (
            <span key={tag} style={{ background: '#f0f0f0', padding: '2px 8px', margin: '2px', borderRadius: '4px' }}>
              {tag}
            </span>
          ))}
        </div>
      )}
    </div>
  )
}
▶ 試してみよう

4. リストのレンダリング:.map()

(1) 基本的な使い方

Array.map() 配列を順に処理し、各要素に対して JSX を返す:

JSX
const fruits = ['Apple', 'Banana', 'Orange', 'Grapes']

function FruitList() {
  return (
    <ul>
      {fruits.map(fruit => (
        <li>{fruit}</li>
      ))}
    </ul>
  )
}
// Output:
// <ul>
//   <li>Apple</li>
//   <li>Banana</li>
//   <li>Orange</li>
//   <li>Grapes</li>
// </ul>

(2) レンダリングオブジェクトの配列

JSX
function ScoreTable() {
  const students = [
    { id: 1, name: 'Alice', score: 92 },
    { id: 2, name: 'Bob', score: 85 },
    { id: 3, name: 'Charlie', score: 78 }
  ]

  return (
    <table border="1" cellPadding="8">
      <thead>
        <tr><th>Name</th><th>Fractions</th><th>Level</th></tr>
      </thead>
      <tbody>
        {students.map(s => (
          <tr key={s.id}>
            <td>{s.name}</td>
            <td>{s.score}</td>
            <td>{s.score >= 90 ? 'Excellent' : s.score >= 80 ? 'Good' : 'Passing Grade'}</td>
          </tr>
        ))}
      </tbody>
    </table>
  )
}

(3) リスト表示のための3つのデータ変換手法

JSX
const items = [
  { id: 1, name: 'React Basics', price: 0 },
  { id: 2, name: 'Vue Getting Started', price: 0 },
  { id: 3, name: 'Node.js Real-World Experience', price: 99 }
]

// 1. Direct Rendering
{items.map(item => <li key={item.id}>{item.name}</li>)}

// 2. Filter First, Then Render(Free Courses)
{items
  .filter(item => item.price === 0)
  .map(item => <li key={item.id}>{item.name}(Free)</li>)
}

// 3. Extract Rendering Functions
function renderItem(item) {
  return (
    <li key={item.id}>
      {item.name} - ${item.price}
    </li>
  )
}
{items.map(renderItem)}


5. 鍵の重要性

React は、リスト内の各要素を識別するために key を使用します。キーが変更されない場合、React はその DOM ノードを再利用します。キーが変更された場合、React はその DOM ノードを破棄して再作成します。

100%
graph LR
    A[List Changes] --> B{key Changes?}
    B -->|No, key The same| C[Reuse DOM Node<br/>Update only the changes]
    B -->|Yes, key It's changed| D[Delete Old Nodes<br/>Create a New Node]
    B -->|No, None key| E[Render All Over]
主要戦略 実績 提言
データベースの一意ID ✅ 最適 ⭐⭐⭐
一意の文字列(UUID) ✅ 最高 ⭐⭐⭐
配列のインデックス ⚠️ リスクあり(リストが変更されると状態が不整合になる可能性がある)
乱数 ❌ 範囲(レンダリングごとに異なる)
キーが渡されていない ❌ パフォーマンスが低い

(1) ▶ サンプル:キーがある場合とない場合

JSX
// ============================================
// Example:key Impact on the Input Field's State
// ============================================

function KeyDemo() {
  const [items, setItems] = React.useState([
    { id: 'a', text: 'Task A' },
    { id: 'b', text: 'Task B' },
    { id: 'c', text: 'Task C' }
  ])

  function shuffle() {
    setItems([...items].sort(() => Math.random() - 0.5))
  }

  return (
    <div>
      <button onClick={shuffle}>Randomize the order</button>
      <p>✅ has  key(id Stable,The content in the input field remains intact):</p>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.text} <input placeholder="Enter content" />
          </li>
        ))}
      </ul>
      
      <p>❌ Without key(or  index As key,The text in the input field appears jumbled):</p>
      <ul>
        {items.map((item, index) => (
          <li key={index}>
            {item.text} <input placeholder="The content may be misaligned" />
          </li>
        ))}
      </ul>
    </div>
  )
}
// Click"Randomize the order"after ,has  key The content in the list input field remains in order
// use  index List of,The content in the input field and the text are misaligned
▶ 試してみよう

6. 空の状態と読み込み中の状態

ステータス 状態 UIの動作 対応
読み込み中 loading === true スピナー/スケルトン画面 if (loading) return <Spinner />
読み込みに失敗しました error !== null エラーメッセージ + 再試行ボタン if (error) return <ErrorView />
空のデータ data.length === 0 空の状態のイラスト+コピー if (!data.length) return <Empty />
通常 上記のいずれでもない データ一覧を表示 デフォルト:通常のUIに戻る

実際のプロジェクトでは、データを読み込む際に処理しなければならない3つの状態があります:

JSX
// ============================================
// Example:Complete List Component(3 Handling Different States)
// ============================================

function ArticleList() {
  const [articles, setArticles] = React.useState([])
  const [isLoading, setIsLoading] = React.useState(true)
  const [error, setError] = React.useState(null)

  // Simulated Loading(Actually, it is API Call)
  React.useEffect(() => {
    fetch('/api/articles')
      .then(res => {
        if (!res.ok) throw new Error('Network request failed')
        return res.json()
      })
      .then(data => {
        setArticles(data)
        setIsLoading(false)
      })
      .catch(err => {
        setError(err.message)
        setIsLoading(false)
      })
  }, [])

  // 1. Loading...
  if (isLoading) {
    return (
      <div style={{ textAlign: 'center', padding: '40px' }}>
        <div className="spinner" />
        <p>Loading article...</p>
      </div>
    )
  }

  // 2. Failed to load
  if (error) {
    return (
      <div style={{ textAlign: 'center', padding: '40px', color: '#ff4d4f' }}>
        <p>❌ Failed to load:{error}</p>
        <button onClick={() => window.location.reload()}>Reload</button>
      </div>
    )
  }

  // 3. Empty data
  if (articles.length === 0) {
    return (
      <div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
        <p>📭 No articles available</p>
        <p>No one has posted an article yet.,Go ahead and write your first one!!</p>
      </div>
    )
  }

  // 4. Normal Rendering
  return (
    <div>
      <h3>List of Articles(Total  {articles.length}  articles)</h3>
      {articles.map(article => (
        <ArticleCard key={article.id} article={article} />
      ))}
    </div>
  )
}


7. 完全な例:商品カテゴリと検索

JSX
// ============================================
// Complete Example:Product List(Conditional Rendering + Comprehensive Guide to List Rendering)
// Features:Filter by Category、Search、Prompt for empty results
// ============================================

function ShopPage() {
  const [products] = React.useState([
    { id: 1, name: 'Wireless Mouse', category: 'Electronics', price: 89, sales: 2000 },
    { id: 2, name: 'Mechanical Keyboard', category: 'Electronics', price: 299, sales: 1500 },
    { id: 3, name: 'Notebook', category: 'Office', price: 15, sales: 5000 },
    { id: 4, name: 'Office Chair', category: 'Furniture', price: 899, sales: 800 },
    { id: 5, name: 'Desk Lamp', category: 'Furniture', price: 129, sales: 3000 },
    { id: 6, name: 'Monitor', category: 'Electronics', price: 1599, sales: 600 }
  ])
  const [search, setSearch] = React.useState('')
  const [category, setCategory] = React.useState('All')

  // Filtering Logic
  const filtered = products.filter(p => {
    const matchSearch = p.name.includes(search)
    const matchCategory = category === 'All' || p.category === category
    return matchSearch && matchCategory
  })

  // Get the list of categories(Remove duplicates)
  const categories = ['All', ...new Set(products.map(p => p.category))]

  return (
    <div style={{ maxWidth: '800px', margin: '0 auto' }}>
      <h2>🛒 Product List</h2>
      
      {/* Search box */}
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
        placeholder="Search by Product Name..."
        style={{ width: '100%', padding: '8px', marginBottom: '12px', border: '1px solid #d9d9d9', borderRadius: '4px' }}
      />

      {/* Category Button(List Rendering) */}
      <div style={{ marginBottom: '12px' }}>
        {categories.map(cat => (
          <button
            key={cat}
            onClick={() => setCategory(cat)}
            style={{
              padding: '6px 16px', margin: '0 4px',
              backgroundColor: category === cat ? '#1890ff' : '#f0f0f0',
              color: category === cat ? 'white' : '#333',
              border: 'none', borderRadius: '4px', cursor: 'pointer'
            }}
          >
            {cat}
          </button>
        ))}
      </div>

      {/* Results Summary */}
      {search || category !== 'All' ? (
        <p style={{ color: '#666' }}>
          {search && `Search"${search}" `}
          {category !== 'All' && `/${category} `}
          Total  {filtered.length} Items
        </p>
      ) : null}

      {/* Empty result */}
      {filtered.length === 0 ? (
        <div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
          <p>😕 No matching products were found.</p>
          <button
            onClick={() => { setSearch(''); setCategory('All') }}
            style={{ padding: '8px 16px', marginTop: '8px', cursor: 'pointer' }}
          >
            Clear All Filters
          </button>
        </div>
      ) : (
        /* Product Grid(List Rendering) */
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px' }}>
          {filtered.map(product => (
            <div key={product.id} style={{ border: '1px solid #eee', borderRadius: '8px', padding: '16px' }}>
              <div style={{ height: '120px', backgroundColor: '#f5f5f5', borderRadius: '4px', marginBottom: '8px' }} />
              <h4>{product.name}</h4>
              <p style={{ color: '#ff4d4f', fontWeight: 'bold', fontSize: '18px' }}>${product.price}</p>
              <p style={{ color: '#999', fontSize: '13px' }}>Sold {product.sales}  items</p>
              <span style={{ background: '#f0f5ff', color: '#1890ff', padding: '2px 8px', borderRadius: '4px', fontSize: '12px' }}>
                {product.category}
              </span>
            </div>
          ))}
        </div>
      )}
    </div>
  )
}

期待される出力:検索フィルタリング、カテゴリフィルタリング、検索結果がない場合の通知、および統計機能を備えた、完全な商品一覧ページ。


(1) ▶ サンプル:フォームフィールドの動的レンダリング

JSX
function DynamicForm() {
  const [formType, setFormType] = useState('personal')
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    company: '',
    taxId: '',
    budget: '',
  })

  function handleChange(e) {
    const { name, value } = e.target
    setFormData(prev => ({ ...prev, [name]: value }))
  }

  const personalFields = (
    <>
      <input name="name" value={formData.name} onChange={handleChange} placeholder="Name" />
      <input name="email" value={formData.email} onChange={handleChange} placeholder="Email" />
    </>
  )

  const businessFields = (
    <>
      <input name="company" value={formData.company} onChange={handleChange} placeholder="Company" />
      <input name="taxId" value={formData.taxId} onChange={handleChange} placeholder="Tax ID" />
      <input name="budget" value={formData.budget} onChange={handleChange} placeholder="Budget ($)" />
    </>
  )

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>Registration</h3>
      <div style={{ marginBottom: 12 }}>
        {['personal', 'business'].map(type => (
          <button
            key={type}
            onClick={() => setFormType(type)}
            style={{
              padding: '6px 16px', marginRight: 8, cursor: 'pointer',
              backgroundColor: formType === type ? '#1890ff' : '#f0f0f0',
              color: formType === type ? 'white' : '#333', border: 'none', borderRadius: 4,
            }}
          >
            {type === 'personal' ? 'Personal' : 'Business'}
          </button>
        ))}
      </div>
      {formType === 'personal' ? personalFields : businessFields}
      <button style={{ marginTop: 12, padding: '8px 24px', cursor: 'pointer' }}>Submit</button>
    </div>
  )
}
▶ 試してみよう

(2) ▶ サンプル:リストの検索、フィルタリング、および並べ替え

JSX
function FilterableList() {
  const [items] = useState([
    { id: 1, name: 'React', category: 'Frontend', stars: 220 },
    { id: 2, name: 'Django', category: 'Backend', stars: 78 },
    { id: 3, name: 'Vue', category: 'Frontend', stars: 207 },
    { id: 4, name: 'Express', category: 'Backend', stars: 63 },
    { id: 5, name: 'Tailwind', category: 'CSS', stars: 80 },
  ])
  const [search, setSearch] = useState('')
  const [sortKey, setSortKey] = useState('name')

  const filtered = items
    .filter(i => i.name.toLowerCase().includes(search.toLowerCase()))
    .sort((a, b) => sortKey === 'stars' ? b.stars - a.stars : a.name.localeCompare(b.name))

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <input value={search} onChange={e => setSearch(e.target.value)}
        placeholder="Search frameworks..." style={{ width: '100%', padding: 8, borderRadius: 4, marginBottom: 8 }} />
      <div style={{ marginBottom: 8 }}>
        <button onClick={() => setSortKey('name')} style={{ padding: '4px 12px', marginRight: 4, cursor: 'pointer', background: sortKey === 'name' ? '#1890ff' : '#f0f0f0', color: sortKey === 'name' ? 'white' : '#333', border: 'none', borderRadius: 4 }}>A-Z</button>
        <button onClick={() => setSortKey('stars')} style={{ padding: '4px 12px', cursor: 'pointer', background: sortKey === 'stars' ? '#1890ff' : '#f0f0f0', color: sortKey === 'stars' ? 'white' : '#333', border: 'none', borderRadius: 4 }}>Stars</button>
      </div>
      {filtered.length === 0 ? (
        <p style={{ color: '#999', textAlign: 'center' }}>No results</p>
      ) : (
        <ul style={{ listStyle: 'none', padding: 0 }}>
          {filtered.map(i => <li key={i.id} style={{ padding: 8, borderBottom: '1px solid #f0f0f0', display: 'flex', justifyContent: 'space-between' }}><span>{i.name} <small style={{ color: '#999' }}>({i.category})</small></span><span>⭐ {i.stars}k</span></li>)}
        </ul>
      )}
    </div>
  )
}
▶ 試してみよう

(3) ▶ サンプル:ネストされたリストのレンダリング—カテゴリ別商品の表示

JSX
function CategorizedProducts() {
  const categories = [
    { id: 'electronics', name: 'Electronics', products: [
      { id: 1, name: 'Keyboard', price: 79 },
      { id: 2, name: 'Monitor', price: 399 },
    ]},
    { id: 'furniture', name: 'Furniture', products: [
      { id: 3, name: 'Desk', price: 249 },
      { id: 4, name: 'Chair', price: 189 },
    ]},
    { id: 'books', name: 'Books', products: [] },
  ]

  return (
    <div style={{ maxWidth: 500, margin: '0 auto' }}>
      {categories.map(cat => (
        <div key={cat.id} style={{ marginBottom: 16 }}>
          <h3 style={{ borderBottom: '2px solid #1890ff', paddingBottom: 4 }}>{cat.name}</h3>
          {cat.products.length === 0 ? (
            <p style={{ color: '#999', fontStyle: 'italic', paddingLeft: 16 }}>No products yet</p>
          ) : (
            <ul style={{ listStyle: 'none', padding: 0, paddingLeft: 16 }}>
              {cat.products.map(p => (
                <li key={p.id} style={{ padding: 4, display: 'flex', justifyContent: 'space-between' }}>
                  <span>{p.name}</span><span>${p.price}</span>
                </li>
              ))}
            </ul>
          )}
        </div>
      ))}
    </div>
  )
}
▶ 試してみよう

❓ よくある質問

Q .map() はネストできますか?複数の .map() 呼び出しを使用する場合、キーはどのように扱われますか?
A はい、ネストできます。.map() の各レベルには、それぞれ独自のキーが必要です。例えば、「カテゴリー → 商品: cats.map(c => <div key={c.id}><h3>{c.name}</h3>{c.products.map(p => <span key={p.id}>{p.name}</span>)}</div>)」をレンダリングする場合などです。異なるレベルのキーは同じでも構いません(それぞれの配列内で一意である限り)。
Q 三項演算子と && 構文、どちらが優れていますか?
A それぞれに適した用途があります。&& は「条件が満たされた場合は表示し、満たされない場合は何も表示しない」場合に適しています。三項演算子 ? : は「条件 A が満たされた場合はコンポーネント X を表示し、条件 B が満たされた場合はコンポーネント Y を表示する」場合に適しています。三項演算子を使って「条件が満たされない場合は何も表示しない」ようにしたい場合、構文は condition ? <Comp /> : null になりますが、この場合は && の方が簡潔です。ただし、&& には「0 レンダリングの落とし穴」があることに注意してください。つまり、0 && <Comp /> は 0 をレンダリングしてしまいます。

📖 まとめ


📝 練習問題

  1. 基本問題(難易度 ⭐)tempのプロップスに基づいて、気温が35°Cより高い場合は赤色の🔥を、15~35°Cの場合は黄色の☀️を、15°Cより低い場合は青色の❄️を表示するTemperatureDisplayコンポーネントを作成してください。
  2. 上級問題(難易度 ⭐⭐):2つのプロップ(todos(タスクの配列)とfilter(all/completed/uncompleted))を受け取るTodoFilterコンポーネントを作成し、状態に基づいたフィルタリングを実装してください。
  3. 課題(難易度:⭐⭐⭐)PaginationtotalPagescurrentPageonPageChange のプロパティを受け取り、ページ番号ボタンをレンダリングする Pagination コンポーネントを作成してください。このコンポーネントは、最初と最後のページ、および省略記号に対応している必要があります。
Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%