React: 条件渲染与列表渲染

最后更新:2026-08-26

条件渲染就像 交通信号灯——根据状态决定显示什么;列表渲染就像 流水线生产——一份数据清单,批量生成相同的 UI 组件。


1. 你将学到



2. 一个电商平台的前端故事

(1) 痛点:商品列表的 6 种状态

Alice 在做一个电商平台的商品列表页。这个页面有 6 种显示状态,每次状态变化都要手动操作 DOM:

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

  if (state.isLoading) {
    container.innerHTML = '<div class="loading">加载中...</div>'
  } else if (state.error) {
    container.innerHTML = '<div class="error">加载失败</div>'
  } else if (!state.isLoggedIn) {
    container.innerHTML = '<div>请登录</div>'
  } else if (state.products.length === 0) {
    container.innerHTML = '<div>暂无商品</div>'
  } else {
    let html = ''
    state.products.forEach(p => {
      html += `<div class="card"><h3>${p.name}</h3><p>$${p.price}</p></div>`
    })
    container.innerHTML = html
  }
}

Alice 的问题:每次状态变化都要手动清空容器、拼接 HTML,30 行代码只做了一件事——显示当前状态

(2) React 的条件渲染解法

JSX
function ProductList({ user, products, isLoading, error }) {
  // 每个状态独立成 if 分支,互不干扰
  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 二选一(有 else 分支) 嵌套三元可读性差,避免超过 2 层
逻辑与 && condition && A 满足才显示(无 else) 0 && <Comp /> 会渲染 0,用 > 0
逻辑或 ` ` `value

(1) 三元运算符 ? :

适合"二选一"的场景:

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

// isActive=true  → <span>🟢 在线</span>
// isActive=false → <span>🔴 离线</span>
▶ 试一试

(2) 逻辑与 &&

适合"满足条件才显示,否则不显示":

JSX
function Notification({ count }) {
  return (
    <div className="notification-icon">
      🔔
      {/* count > 0 时显示红点,否则什么都不显示 */}
      {count > 0 && (
        <span className="badge">{count > 99 ? '99+' : count}</span>
      )}
    </div>
  )
}

// count=0  → 只显示 🔔
// count=5  → 显示 🔔 和 "5"
▶ 试一试
⚠️ 陷阱:不要写 {items.length && <List />}。当 items.length 为 0 时,0 && <List /> 的结果是 0,React 会把 0 渲染到页面上。正确写法是 {items.length > 0 && <List />}

(3) 逻辑或 ||

适合"提供兜底值":

JSX
function UserProfile({ user }) {
  return (
    <div>
      <h2>{user.nickname || '匿名用户'}</h2>
      <p>{user.bio || '这个人很懒,什么都没写...'}</p>
      <p>位置:{user.location || '未知'}</p>
    </div>
  )
}
▶ 试一试

▶ 示例:三种条件渲染同场对比

JSX
// ============================================
// 示例:用户面板中三种条件渲染的综合运用
// ============================================

function UserPanel({ user }) {
  return (
    <div className="panel">
      {/* 1. 三元:二选一 */}
      <h2>
        {user.isVIP ? '⭐ 尊贵会员' : '普通用户'}
      </h2>

      {/* 2. &&:满足才显示 */}
      {user.isVIP && (
        <p>会员到期:{user.vipExpireDate}</p>
      )}

      {/* 3. ||:兜底值 */}
      <p>签名:{user.slogan || '此人很酷,没有签名'}</p>

      {/* 组合使用:标签列表 */}
      {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 = ['苹果', '香蕉', '橙子', '葡萄']

function FruitList() {
  return (
    <ul>
      {fruits.map(fruit => (
        <li>{fruit}</li>
      ))}
    </ul>
  )
}
// 输出:
// <ul>
//   <li>苹果</li>
//   <li>香蕉</li>
//   <li>橙子</li>
//   <li>葡萄</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>姓名</th><th>分数</th><th>等级</th></tr>
      </thead>
      <tbody>
        {students.map(s => (
          <tr key={s.id}>
            <td>{s.name}</td>
            <td>{s.score}</td>
            <td>{s.score >= 90 ? '优秀' : s.score >= 80 ? '良好' : '及格'}</td>
          </tr>
        ))}
      </tbody>
    </table>
  )
}
▶ 试一试

(3) 列表渲染的 3 种数据转换技巧

JSX
const items = [
  { id: 1, name: 'React 基础', price: 0 },
  { id: 2, name: 'Vue 入门', price: 0 },
  { id: 3, name: 'Node.js 实战', price: 99 }
]

// 1. 直接渲染
{items.map(item => <li key={item.id}>{item.name}</li>)}

// 2. 过滤后再渲染(免费课程)
{items
  .filter(item => item.price === 0)
  .map(item => <li key={item.id}>{item.name}(免费)</li>)
}

// 3. 提取渲染函数
function renderItem(item) {
  return (
    <li key={item.id}>
      {item.name} - ${item.price}
    </li>
  )
}
{items.map(renderItem)}
▶ 试一试

5. key 的重要性

React 用 key 识别列表中的每个元素。key 稳定时,React 复用 DOM 节点;key 变化时,React 销毁重建。

100%
graph LR
    A[列表变化] --> B{key 变化?}
    B -->|No, key 相同| C[复用 DOM 节点<br/>只更新变化内容]
    B -->|Yes, key 变了| D[销毁旧节点<br/>创建新节点]
    B -->|No, 没有 key| E[全部重新渲染]
key 策略 性能 推荐
数据库唯一 ID ✅ 最优 ⭐⭐⭐
唯一字符串(uuid) ✅ 最优 ⭐⭐⭐
数组索引 index ⚠️ 有风险(列表变化时状态错乱)
随机数 ❌ 极差(每次渲染都变)
不传 key ❌ 性能差

▶ 示例:有 key vs 无 key

JSX
// ============================================
// 示例:key 对输入框状态的影响
// ============================================

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

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

  return (
    <div>
      <button onClick={shuffle}>打乱顺序</button>
      <p>✅ 有 key(id 稳定,输入框内容不乱):</p>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.text} <input placeholder="输入内容" />
          </li>
        ))}
      </ul>
      
      <p>❌ 无 key(或 index 作为 key,输入框内容会错乱):</p>
      <ul>
        {items.map((item, index) => (
          <li key={index}>
            {item.text} <input placeholder="内容会错位" />
          </li>
        ))}
      </ul>
    </div>
  )
}
// 点击"打乱顺序"后,有 key 的列表输入框内容不乱
// 用 index 的列表,输入框内容和文字会错位
▶ 试一试

6. 空状态和加载状态

状态 条件 UI 表现 处理方式
加载中 loading === true Spinner / 骨架屏 if (loading) return <Spinner />
加载失败 error !== null 错误信息 + 重试按钮 if (error) return <ErrorView />
空数据 data.length === 0 空状态插图 + 引导文案 if (!data.length) return <Empty />
正常 以上都不是 渲染数据列表 默认 return 正常 UI

真实项目中,数据加载有 3 种必须处理的状态:

JSX
// ============================================
// 示例:完整的列表组件(3 种状态处理)
// ============================================

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

  // 模拟加载(实际是 API 调用)
  React.useEffect(() => {
    fetch('/api/articles')
      .then(res => {
        if (!res.ok) throw new Error('网络请求失败')
        return res.json()
      })
      .then(data => {
        setArticles(data)
        setIsLoading(false)
      })
      .catch(err => {
        setError(err.message)
        setIsLoading(false)
      })
  }, [])

  // 1. 加载中
  if (isLoading) {
    return (
      <div style={{ textAlign: 'center', padding: '40px' }}>
        <div className="spinner" />
        <p>正在加载文章...</p>
      </div>
    )
  }

  // 2. 加载失败
  if (error) {
    return (
      <div style={{ textAlign: 'center', padding: '40px', color: '#ff4d4f' }}>
        <p>❌ 加载失败:{error}</p>
        <button onClick={() => window.location.reload()}>重新加载</button>
      </div>
    )
  }

  // 3. 空数据
  if (articles.length === 0) {
    return (
      <div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
        <p>📭 暂无文章</p>
        <p>还没有人发布文章,快来写第一篇吧!</p>
      </div>
    )
  }

  // 4. 正常渲染
  return (
    <div>
      <h3>文章列表(共 {articles.length} 篇)</h3>
      {articles.map(article => (
        <ArticleCard key={article.id} article={article} />
      ))}
    </div>
  )
}


7. 完整示例:商品分类与搜索

JSX
// ============================================
// 完整示例:商品列表(条件渲染 + 列表渲染综合)
// 功能:按分类筛选、搜索、空结果提示
// ============================================

function ShopPage() {
  const [products] = React.useState([
    { id: 1, name: '无线鼠标', category: '电子', price: 89, sales: 2000 },
    { id: 2, name: '机械键盘', category: '电子', price: 299, sales: 1500 },
    { id: 3, name: '笔记本', category: '办公', price: 15, sales: 5000 },
    { id: 4, name: '办公椅', category: '家具', price: 899, sales: 800 },
    { id: 5, name: '台灯', category: '家具', price: 129, sales: 3000 },
    { id: 6, name: '显示器', category: '电子', price: 1599, sales: 600 }
  ])
  const [search, setSearch] = React.useState('')
  const [category, setCategory] = React.useState('全部')

  // 过滤逻辑
  const filtered = products.filter(p => {
    const matchSearch = p.name.includes(search)
    const matchCategory = category === '全部' || p.category === category
    return matchSearch && matchCategory
  })

  // 获取分类列表(去重)
  const categories = ['全部', ...new Set(products.map(p => p.category))]

  return (
    <div style={{ maxWidth: '800px', margin: '0 auto' }}>
      <h2>🛒 商品列表</h2>
      
      {/* 搜索框 */}
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
        placeholder="搜索商品名称..."
        style={{ width: '100%', padding: '8px', marginBottom: '12px', border: '1px solid #d9d9d9', borderRadius: '4px' }}
      />

      {/* 分类按钮(列表渲染) */}
      <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>

      {/* 结果统计 */}
      {search || category !== '全部' ? (
        <p style={{ color: '#666' }}>
          {search && `搜索"${search}" `}
          {category !== '全部' && `/${category} `}
          共 {filtered.length} 件商品
        </p>
      ) : null}

      {/* 空结果 */}
      {filtered.length === 0 ? (
        <div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
          <p>😕 没有找到匹配的商品</p>
          <button
            onClick={() => { setSearch(''); setCategory('全部') }}
            style={{ padding: '8px 16px', marginTop: '8px', cursor: 'pointer' }}
          >
            清除全部筛选
          </button>
        </div>
      ) : (
        /* 商品网格(列表渲染) */
        <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' }}>已售 {product.sales} 件</p>
              <span style={{ background: '#f0f5ff', color: '#1890ff', padding: '2px 8px', borderRadius: '4px', fontSize: '12px' }}>
                {product.category}
              </span>
            </div>
          ))}
        </div>
      )}
    </div>
  )
}

预期输出:完整的商品列表页,支持搜索过滤、分类筛选、空结果提示和统计信息。


▶ 示例 5:动态表单字段渲染

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>
  )
}
逻辑代码 49 行(超过 40 行限制,仅展示)

▶ 示例 3:列表搜索过滤与排序

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>
  )
}
▶ 试一试

▶ 示例 4:嵌套列表渲染——分类商品展示

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 三元表达式和 && 写法哪个更好?
A 各有场景:&& 适合"满足条件就显示,不满足就什么都不渲染";三元 ? : 适合"条件 A 显示组件 X,条件 B 显示组件 Y"。如果"不满足时什么都不显示"用三元写就是 condition ? <Comp /> : null,此时用 && 更简洁。但注意 && 的 0 渲染陷阱——0 && <Comp /> 会渲染出 0。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建一个 TemperatureDisplay 组件,根据 temp Props:>35°C 显示 🔥 红色,15-35°C 显示 ☀️ 黄色,<15°C 显示 ❄️ 蓝色。
  2. 进阶题(难度⭐⭐):创建一个 TodoFilter 组件,接收 todos(任务数组)和 filter(全部/已完成/未完成)两个 Props,实现按状态过滤显示。
  3. 挑战题(难度⭐⭐⭐):创建一个 Pagination 组件,接收 totalPagescurrentPageonPageChange Props,渲染页码按钮,支持首尾页、省略号。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏