React: 条件渲染与列表渲染

最后更新:2026-10-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>
  )
}
▶ 试一试

输出:

TEXT
并排对比:HTML 语法与 JSX 语法展示等价的标记。使用 interface 定义 props 的 TypeScript 类型 React 组件

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

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

输出:

TEXT
若为 VIP:"⭐ Premium Member" + 会员到期时间。若不是:"Regular User"。签名:用户文本或兜底内容。标签仅在存在时显示。


4. 列表渲染:.map()

(1) 基础用法

Array.map() 遍历数组,为每个元素返回一个 JSX:

▶ 试一试

(2) 渲染对象数组

▶ 试一试

(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[全部重新渲染]

输出:

TEXT
副标题:"        {user.isVIP ? '⭐ Premium Member' : 'Regular User'}      "。显示:"0 && ("
key 策略 性能 推荐
数据库唯一 ID ✅ 最优 ⭐⭐⭐
唯一字符串(uuid) ✅ 最优 ⭐⭐⭐
数组索引 index ⚠️ 有风险(列表变化时状态错乱) ⭐
随机数 ❌ 极差(每次渲染都变) ❌
不传 key ❌ 性能差 ❌

▶ 示例:有 key vs 无 key

▶ 试一试

输出:

TEXT
两个带输入字段的列表。点击 "Randomize":✅ 列表(key=id)保持输入值对齐;❌ 列表(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. 完整示例:商品分类与搜索

▶ 展开运行示例(81 行)
▶ 试一试

输出:

TEXT
显示:"Randomize the order"。按钮:Randomize the order。输入框:Enter content、The content may be misaligned

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


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

输出:

TEXT
个人表单:Name + Email 字段。企业表单:Company + Tax ID + Budget 字段。通过按钮切换。底部为提交按钮。

输出:

TEXT
搜索输入框过滤条目。排序按钮:A-Z(按名称)/ Stars(按评分)。当前排序以蓝色高亮。过滤 + 排序后的列表响应式更新

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

▶ 试一试

输出:

TEXT
框架:React ⭐220k、Vue ⭐207k、Tailwind ⭐80k、Django ⭐78k、Express ⭐63k。搜索过滤;A-Z/Stars 排序切换。

输出:

TEXT
显示 "{cat.name}"

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

▶ 试一试

输出:

TEXT
Electronics: Keyboard $79、Monitor $399
Furniture: Desk $249、Chair $189
Books: "No products yet"(斜体空状态)

❓ 常见问题

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 组件,接收 totalPages、currentPage、onPageChange Props,渲染页码按钮,支持首尾页、省略号。
Web-Tutorial.com

Web-Tutorial 技术团队

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

100%

🙏 帮我们做得更好

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

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