React 条件渲染与列表渲染完全指南

小飞兽 React 188 次阅读 2026-06-12

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 要求数据不可变——不要在渲染过程中直接 pushsplicesort 修改原始数组。应该用 filtermapslice 等返回新数组的方式。

// 错误:直接修改原数组
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]()

---