React 条件渲染与列表渲染完全指南
React 条件渲染与列表渲染完全指南
一、Introduction
条件渲染和列表渲染是 React 中最基本也是最核心的 UI 逻辑。条件渲染让你根据应用状态动态决定显示什么内容,列表渲染让你把数组数据映射为一组 UI 元素。两者结合构成了绝大多数 React 应用的 UI 层基础。
但看似简单的背后,条件渲染有多种写法(&&、三元、提前返回),选错会导致渲染 Bug;列表渲染如果不正确使用 key 属性,会导致虚拟 DOM diff 算法失效,引发严重的性能问题。本文用完整示例讲解所有正确用法。
---
二、条件渲染
#### 2.1 多种写法及适用场景
import React, { useState } from 'react';
// 写法一:三元表达式——适用于简单的二选一渲染
function AuthStatus({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<button>退出登录</button>
) : (
<button>登录 / 注册</button>
)}
</div>
);
}
// 写法二:&& 运算符——适用于条件成立时才渲染内容
function Notification({ unreadCount }) {
return (
<div>
{unreadCount > 0 && (
<span className="badge">
您有 {unreadCount} 条新消息
</span>
)}
</div>
);
}
// 写法三:提前返回——适用于"不满足条件就不渲染其余内容"的场景
function UserProfile({ user }) {
// 加载中时直接返回 null,不渲染任何东西
if (!user) {
return null;
}
// 数据不存在时显示占位
if (user.isDeleted) {
return <div>该用户已注销</div>;
}
// 正常渲染
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}
// 写法四:变量预先存储 JSX——适用于复杂条件逻辑
function PricingPlan({ plan, isAnnual }) {
let priceContent;
if (plan === 'free') {
priceContent = <span>免费</span>;
} else if (plan === 'pro') {
const basePrice = 99;
priceContent = isAnnual ? (
<span>年付 ¥{basePrice * 12 * 0.8}(8折)</span>
) : (
<span>月付 ¥{basePrice}</span>
);
} else {
priceContent = <span>企业版 - 联系销售</span>;
}
return <div className="pricing">{priceContent}</div>;
}
export { AuthStatus, Notification, UserProfile, PricingPlan };
#### 2.2 一个常见的 && 陷阱
// 错误用法:count 为 0 时,下面的代码会渲染出 "0"
function BugExample({ count }) {
return (
<div>
{count && <span>新消息:{count}</span>}
{/* 当 count === 0 时,渲染结果是 "0"(数字0),不是 null */}
{/* 这是因为 0 && JSX 返回 0,而不是 null */}
</div>
);
}
// 正确做法:确保左侧是布尔值
function CorrectExample({ count }) {
return (
<div>
{count > 0 && <span>新消息:{count}</span>}
{/* count > 0 返回 true/false,不会出现 0 的问题 */}
</div>
);
}
---
三、列表渲染
#### 3.1 基础列表渲染
import React from 'react';
function BasicList() {
const fruits = ['苹果', '香蕉', '橙子', '草莓', '葡萄'];
return (
<ul>
{/* map 遍历数组,返回 JSX 数组 */}
{fruits.map((fruit, index) => (
// key 必须是唯一且稳定的!优先用 id,不用 index
<li key={fruit + index}>
{index + 1}. {fruit}
</li>
))}
</ul>
);
}
#### 3.2 对象数组的列表渲染
import React from 'react';
function ProductList() {
const products = [
{ id: 'p001', name: 'iPhone 15', price: 6999, stock: 120 },
{ id: 'p002', name: 'MacBook Pro', price: 15999, stock: 45 },
{ id: 'p003', name: 'AirPods Pro', price: 1899, stock: 0 },
{ id: 'p004', name: 'iPad Air', price: 4399, stock: 88 },
];
return (
<table style={{ borderCollapse: 'collapse', width: '100%', maxWidth: 600 }}>
<thead>
<tr>
<th style={thStyle}>商品名称</th>
<th style={thStyle}>价格</th>
<th style={thStyle}>库存</th>
<th style={thStyle}>状态</th>
</tr>
</thead>
<tbody>
{products.map(product => (
<tr key={product.id}>
<td style={tdStyle}>{product.name}</td>
<td style={tdStyle}>¥{product.price}</td>
<td style={tdStyle}>{product.stock}</td>
<td style={tdStyle}>
{product.stock > 0 ? (
<span style={{ color: 'green' }}>有货</span>
) : (
<span style={{ color: 'red' }}>缺货</span>
)}
</td>
</tr>
))}
</tbody>
</table>
);
}
const thStyle = { border: '1px solid #ddd', padding: 8, textAlign: 'left', background: '#f5f5f5' };
const tdStyle = { border: '1px solid #ddd', padding: 8 };
export default ProductList;
#### 3.3 列表渲染中的条件过滤
import React, { useState } from 'react';
function FilterableProductList() {
const [search, setSearch] = useState('');
const products = [
{ id: 1, name: 'React 入门', category: '前端', price: 99 },
{ id: 2, name: 'Vue 进阶', category: '前端', price: 129 },
{ id: 3, name: 'Node.js 实战', category: '后端', price: 149 },
{ id: 4, name: 'Python 数据分析', category: '数据', price: 199 },
{ id: 5, name: 'React 性能优化', category: '前端', price: 89 },
];
// 在渲染前过滤数据,而不是在 JSX 中过滤(性能更好)
const filteredProducts = products.filter(p =>
p.name.toLowerCase().includes(search.toLowerCase())
);
return (
<div>
<input
type="text"
placeholder="搜索商品..."
value={search}
onChange={e => setSearch(e.target.value)}
style={{ padding: 8, width: 200, marginBottom: 16 }}
/>
{filteredProducts.length === 0 ? (
<p>没有找到匹配的商品</p>
) : (
<ul>
{filteredProducts.map(product => (
<li key={product.id}>
{product.name}({product.category})- ¥{product.price}
</li>
))}
</ul>
)}
</div>
);
}
export default FilterableProductList;
---
四、运行效果说明
AuthStatus:isLoggedIn 为 true 时显示"退出登录",false 时显示"登录/注册"按钮,二选一渲染。
Notification:unreadCount > 0 时显示红色徽章,count === 0 时不渲染任何内容(不是渲染0)。
ProductList:将4个商品渲染为表格行,有货显示绿色"有货",库存为0显示红色"缺货"。
FilterableProductList:输入搜索词,列表实时过滤,只显示匹配的商品;搜索词为空显示全部;无匹配显示"没有找到"提示。
---
五、常见问题(FAQ)
#### Q1:为什么列表渲染必须使用 key 属性?
key 帮助 React 的 diff 算法精确识别每个列表项。当列表项增删或排序时,React 通过 key 判断哪些项是新建的、哪些是移动的、哪些是删除的。如果不用 key 或使用 index 作为 key,在列表中间插入或删除项时,React 可能错误地复用 DOM 元素,导致内容错位、状态混乱。
// 错误:用 index 作为 key,列表排序时会出现 bug
items.map((item, index) => <div key={index}>{item.name}</div>);
// 正确:用稳定的唯一 ID
items.map(item => <div key={item.id}>{item.name}</div>);
#### Q2:为什么不能用 Math.random() 或 Date.now() 作为 key?
因为每次渲染时这些值都会变化,导致 React 认为所有列表项都是新建的,触发完整的 DOM 重建而不是复用,性能极差。key 必须在多次渲染间保持稳定。
// 错误:每次渲染 key 都变化,列表永远无法复用
items.map(item => <div key={Math.random()}>{item.name}</div>);
// 正确:使用稳定的数据 ID
items.map(item => <div key={item.id}>{item.name}</div>);
#### Q3:可以在渲染时直接修改列表变量吗?
绝对不可以。 React 要求数据不可变——不要在渲染过程中直接 push、splice、sort 修改原始数组。应该用 filter、map、slice 等返回新数组的方式。
// 错误:直接修改原数组
const handleDelete = (id) => {
items.push({ id: items.length + 1, name: '新项目' }); // 违反不可变性
};
// 正确:创建新数组
const handleDelete = (id) => {
setItems(prev => [...prev, { id: prev.length + 1, name: '新项目' }]);
};
---
六、延伸阅读
- [React Hooks 完全指南:useState 与 useEffect 核心用法]()
- [React useRef 完全指南:操作 DOM 与持久化变量]()
- [React 组件设计模式:高阶组件与 Render Props]()
- [React 状态管理演进:从 useState 到 Redux]()
---