React列表渲染与条件渲染及避坑指南
作者:风月说与山鬼
一、条件渲染
条件渲染可以根据组件的状态、数据或权限,决定页面渲染什么内容,是 React 开发中的高频技巧。下面按从简单到高级的顺序,梳理几种常用实现方式。
1. 基础内联判断(最常用)
这类方法直接写在 JSX 的 {} 大括号里,适合处理简单的条件逻辑。
三元运算符
适用场景:二选一(非 A 即 B)。
实际例子:用户登录了显示欢迎语,没登录显示登录按钮。
{isLoggedIn ? <h1>欢迎回来!</h1> : <button>请登录</button>}
逻辑与(&&)
适用场景:满足条件才显示,不满足就不显示(没有 else 分支)。
实际例子:只有当购物车有商品时,才渲染商品列表。
{cartItems.length > 0 && <ShoppingList items={cartItems} />}⚠️ 新手避坑:千万别写成 cartItems.length && ...,如果长度为 0,页面上会直接显示一个数字 0!
| 写法 | 适用场景 | 是否有 else 分支 | 典型示例 |
|---|---|---|---|
三元运算符 ? : | 二选一,非 A 即 B | 是 | {isLoggedIn ? <h1>欢迎回来!</h1> : <button>请登录</button>} |
逻辑与 && | 满足条件才显示,不满足不显示 | 否 | {cartItems.length > 0 && <ShoppingList items={cartItems} />} |
2. 组件外置变量赋值(最清晰)
当条件判断逻辑比较复杂,或者嵌套太多时,直接写在 JSX 里会让代码很难看。这时候我们可以把逻辑“提取”到 return 语句之前。
if…else + 变量
适用场景:多分支逻辑,或者需要在条件判断前后打印日志(console.log)、做复杂计算。
实际例子:根据不同状态渲染不同的 UI。
let content;
if (status === 'loading') {
content = <Spinner />;
} else if (status === 'error') {
content = <ErrorMsg />;
} else {
content = <DataList />;
}
return <div>{content}</div>;switch…case + 变量
适用场景:有多个固定的状态值需要匹配时。
实际例子:电商订单详情页,根据订单状态(待支付、待发货、已完成、已取消)展示不同的操作按钮和提示。
function OrderStatus({ status }) {
// 1. 把复杂的判断逻辑提取成一个函数
const renderStatusUI = () => {
switch (status) {
case 'pending':
return <div className="text-orange-500">⏳ 待支付,请在30分钟内完成付款</div>;
case 'shipped':
return <div className="text-blue-500">🚚 商家已发货,请耐心等待</div>;
case 'completed':
return <div className="text-green-500">✅ 订单已完成,欢迎评价</div>;
default:
return <div className="text-gray-500">❓ 未知订单状态</div>;
}
};
// 2. JSX 变得极其清爽
return (
<div className="order-card">
<h2>订单详情</h2>
{renderStatusUI()}
</div>
);
}3. 高级渲染模式(适合架构与复用)
当你的条件逻辑需要在多个组件中反复使用,或者需要高度解耦时,可以使用以下高级技巧。
立即执行函数(IIFE)
适用场景:必须在 JSX 内部处理复杂的多条件分支(如 switch),不想污染组件外部变量。
实际例子:
<div>
{(() => {
switch (status) {
case 'loading': return <Spinner />;
case 'success': return <DataList />;
default: return null;
}
})()}
</div>高阶组件(HOC)
适用场景:封装通用的条件逻辑,比如权限控制。
实际例子:写一个 withAdminAccess 函数,只有管理员才能看到被包裹的组件,否则显示“无权限”。
// 1. 定义一个权限包装盒
function withAdminAccess(WrappedComponent) {
return function(props) {
const isAdmin = props.userRole === 'admin'; // 假设从 props 获取角色
if (!isAdmin) {
return <div style={{color:'red'}}>🚫 403 无权限:仅管理员可访问此页面</div>;
}
// 权限通过,正常渲染原组件,并把剩下的 props 传下去
return <WrappedComponent {...props} />;
};
}
// 2. 普通的业务组件(它根本不需要关心权限问题)
function UserManagePanel() {
return <div>这里是用户管理列表,包含删除按钮...</div>;
}
// 3. 把组件放进盒子里导出
export default withAdminAccess(UserManagePanel);Render Props(渲染属性)
适用场景:将“如何渲染”的决定权交给父组件,实现细粒度的逻辑共享。
实际例子:获取当前鼠标位置,或者获取用户在线状态。逻辑很复杂,但展示方式千变万化。
// 1. 状态提供者组件
function MouseTracker({ render }) {
const [position, setPosition] = React.useState({ x: 0, y: 0 });
React.useEffect(() => {
const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);
// 核心:把状态作为参数,传给 render 函数
return render(position);
}
// 2. 使用者决定怎么画
function App() {
return (
<MouseTracker render={(pos) => (
<div>
<p>当前鼠标坐标:X={pos.x}, Y={pos.y}</p>
{/* 这里你可以随便画,甚至画个跟随鼠标的图片 */}
</div>
)} />
);
}自定义 Hook
适用场景:将条件判断的逻辑(如媒体查询、权限判断)抽离成可复用的状态逻辑。
实际例子:判断当前设备是不是手机端(媒体查询),或者封装一个通用的“数据请求”逻辑。
// 1. 封装一个判断屏幕宽度的 Hook
function useIsMobile() {
const [isMobile, setIsMobile] = React.useState(window.innerWidth < 768);
React.useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth < 768);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return isMobile; // 只返回一个布尔值
}
// 2. 组件里像用普通变量一样使用它
function NavigationBar() {
const isMobile = useIsMobile();
return (
<nav>
{isMobile ? (
<button>☰ 菜单</button>
) : (
<ul>
<li>首页</li>
<li>关于</li>
</ul>
)}
</nav>
);
}| 模式 | 适用场景 | 特点 |
|---|---|---|
| 立即执行函数(IIFE) | JSX 内部处理复杂多条件分支 | 逻辑内聚,不污染组件外部变量 |
| 高阶组件(HOC) | 封装通用条件逻辑,如权限控制 | 包裹组件复用逻辑 |
| Render Props | 父组件决定“如何渲染” | 细粒度共享渲染逻辑 |
| 自定义 Hook | 复用条件相关状态逻辑 | 组件中使用更简洁 |
二、列表渲染
列表渲染用于把数组数据批量展示为界面元素,如商品卡片、新闻列表、聊天消息等。下面从最常用的 map() 开始,再到 Fragment、循环/归并等进阶写法,最后说明 key 的使用要点。
1. 标准推荐:使用 map() 方法
这是 React 中最常见、最标准的列表渲染方式。无论是商品列表还是新闻列表,99% 的场景都会用到它。
实际例子:渲染一个包含标题和 ID 的列表。
const data = [
{ id: 1, title: 'React基础' },
{ id: 2, title: 'JSX语法' }
];
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
);当列表项的 JSX 结构比较复杂时,建议将其封装成独立的子组件(如 <ListItem />),这样主组件的代码会清爽很多。
map() 本身会返回一个新数组,通常直接放在 JSX 中交给 React 渲染;如果需要复用或二次处理,也可以先存入变量再渲染。
2. 进阶写法:使用 Fragment(避免多余 DOM)
在渲染列表时,有时我们需要为每一项渲染多个平级的标签(比如标题和描述)。如果强行用一个 <div> 包裹,会导致 DOM 树中多出很多无意义的节点。这时可以使用 <React.Fragment>(简写为 <>)。
实际例子:
return (
<>
{data.map(item => (
// 注意:Fragment 必须使用完整写法并加上 key
<React.Fragment key={item.id}>
<h3>{item.title}</h3>
<p>{item.description}</p>
</React.Fragment>
))}
</>
);补充说明:简写形式 <>...</> 无法携带 key,所以列表项需要 key 时,必须使用 <React.Fragment key={...}> 的完整写法。
3. 特殊场景:使用 for 循环或 reduce()
for 循环:在渲染前通过 for 循环将 JSX 元素 push 到一个数组中,然后再渲染该数组。这种方式可行,但不符合 React 声明式的代码风格,不推荐日常使用。
当你需要在渲染列表之前,进行复杂的逻辑控制(比如 break 提前终止、continue 跳过某些项,或者需要同时遍历多个数组)时,map() 会显得力不从心,这时 for 循环就派上用场了。
实际开发场景:假设你要渲染一个“最新 5 条消息”的列表,但后端返回了 100 条数据,且你只想渲染前 5 条。用 map 需要配合 slice,而用 for 循环可以直接 break。
function MessageList({ messages }) {
const items = []; // 1. 准备一个空数组
// 2. 使用 for...of 循环(比传统 for 更现代、易读)
for (const msg of messages) {
// 假设遇到已删除的消息就跳过
if (msg.isDeleted) continue;
items.push(
<li key={msg.id} className="message-item">
{msg.content}
</li>
);
// 核心优势:只要凑够了 5 条,立刻停止循环,节省性能!
if (items.length >= 5) break;
}
// 3. 在 JSX 中直接渲染这个数组
return <ul>{items}</ul>;
}reduce() 方法:当你需要在遍历数组的同时进行累加逻辑或复杂的数据转换时,可以使用 reduce() 来构造 JSX 列表。
当你不仅需要遍历数组生成列表,还需要在遍历的过程中“记住”前一个元素的状态,或者需要对数据进行分组、过滤并转换时,reduce() 是神器。它可以把“数据清洗”和“生成 JSX”合并在一步完成。
实际开发场景:假设你有一个包含日期的任务列表,你需要按日期进行分组渲染(例如:今天、昨天、更早)。reduce() 可以在遍历过程中动态构建出分组后的 JSX 结构。
function TaskList({ tasks }) {
// 使用 reduce 将平铺的任务列表,转换为按日期分组的 JSX 结构
const groupedTasks = tasks.reduce((acc, task) => {
// 获取当前任务的日期标签
const dateLabel = task.dateLabel; // 比如 '今天', '昨天'
// 检查累加器中是否已经有这个日期的分组
const existingGroup = acc.find(group => group.label === dateLabel);
if (existingGroup) {
// 如果已有该分组,直接把任务追加进去
existingGroup.children.push(
<li key={task.id}>{task.title}</li>
);
} else {
// 如果没有,创建一个新的分组并加入累加器
acc.push({
label: dateLabel,
children: [<li key={task.id}>{task.title}</li>]
});
}
return acc; // 必须返回累加器
}, []); // 初始值是一个空数组
// 渲染分组后的结果
return (
<div>
{groupedTasks.map(group => (
<div key={group.label} className="task-group">
<h3>{group.label}</h3>
<ul>{group.children}</ul>
</div>
))}
</div>
);
}| 方式 | 适用场景 | 注意事项 |
|---|---|---|
for 循环 | 需要 break/continue 或同时遍历多个数组 | 代码偏命令式,不优先推荐 |
reduce() | 遍历时分组、过滤、转换、累加 | 必须显式返回累加结果 |
4. 结合条件渲染与嵌套列表
在实际业务中,列表往往不是孤立存在的。
条件渲染结合:在渲染列表前,先判断数组是否有数据。如果没有数据,显示“暂无内容”的占位图。
{items.length > 0 ? (
<ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>
) : (
<p>暂无数据</p>
)}
嵌套列表:当列表项中又包含子列表时,可以在外层 map() 内部再调用一次 map(),并分别为内层元素设置 key。
{categories.map(category => (
<div key={category.id}>
<h3>{category.name}</h3>
<ul>
{category.children.map(child => (
<li key={child.id}>{child.name}</li>
))}
</ul>
</div>
))}
5. 避坑指南
在使用列表渲染时,key 属性是重中之重。React 依靠 key 来识别哪些元素改变了、添加了或删除了,从而优化渲染性能。
- 优先使用唯一 ID:后端返回的数据通常都有 id,这是最优选择。
- 绝对禁止使用
Math.random()作为 key:这会导致每次重新渲染时 key 都不同,React 会销毁所有旧组件并重新创建,不仅导致页面卡顿,还会丢失用户的输入状态。 - 谨慎使用索引
index作为 key:只有在列表是纯静态(不会重新排序、不会插入或删除元素)的简单数组(如纯字符串数组)时,才能用index。一旦列表顺序发生变化,使用index会导致状态错乱等令人困惑的 Bug。
到此这篇关于React列表渲染与条件渲染及避坑指南的文章就介绍到这了,更多相关react列表渲染和条件渲染内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
