React: 条件渲染与列表渲染
最后更新:2026-10-26
条件渲染就像 交通信号灯——根据状态决定显示什么;列表渲染就像 流水线生产——一份数据清单,批量生成相同的 UI 组件。
1. 你将学到
- 使用三元运算符
? :控制显示内容 - 使用
&&和||简化条件渲染 - 使用
.map()渲染列表 - key 属性的重要性和使用规则
- 空状态和加载状态的处理
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 销毁重建。
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。📖 小节
- 条件渲染有三种方式:
? :(二选一)、&&(仅显示)、||(兜底值) - 列表渲染用
.map(),每个 JSX 元素必须绑定唯一的key - 真实项目必须处理 3 种状态:加载中、加载失败、空数据
- 警惕
{items.length && <List />}的 0 渲染陷阱 - key 用唯一稳定的 ID,不要用 index
📝 作业
- 基础题(难度⭐):创建一个
TemperatureDisplay组件,根据tempProps:>35°C 显示 🔥 红色,15-35°C 显示 ☀️ 黄色,<15°C 显示 ❄️ 蓝色。 - 进阶题(难度⭐⭐):创建一个
TodoFilter组件,接收todos(任务数组)和filter(全部/已完成/未完成)两个 Props,实现按状态过滤显示。 - 挑战题(难度⭐⭐⭐):创建一个
Pagination组件,接收totalPages、currentPage、onPageChangeProps,渲染页码按钮,支持首尾页、省略号。