react

条件渲染与列表渲染

By AI-Writer 6 min read

前言

React 的核心哲学之一是UI 是状态的函数。当状态变化时,界面需要根据条件显示不同的内容,或者渲染一个列表中的多个元素。条件渲染和列表渲染是 React 中最常见的需求之一,本文将系统讲解这两类渲染模式及其最佳实践。

条件渲染

if 语句(早期 return)

最直接的方式是在组件内部使用条件判断,决定返回什么内容:

jsx
function AuthStatus({ isLoggedIn }) {
  // 早期返回:未登录时直接返回特定 UI
  if (!isLoggedIn) {
    return <p>请登录以继续</p>;
  }

  return (
    <div>
      <p>欢迎回来!</p>
      <button>进入控制台</button>
    </div>
  );
}

三元运算符

适用于两种状态之间的切换:

jsx
function SubscriptionBadge({ tier }) {
  return (
    <div className="badge">
      {/* 根据 tier 显示不同徽章 */}
      {tier === 'pro' ? (
        <span style={{ color: '#D02020', fontWeight: 'bold' }}>PRO</span>
      ) : tier === 'enterprise' ? (
        <span style={{ color: '#1040C0', fontWeight: 'bold' }}>ENTERPRISE</span>
      ) : (
        <span style={{ color: '#666' }}>FREE</span>
      )}
    </div>
  );
}

逻辑与运算符(&&)

适用于”满足条件时渲染,否则什么都不渲染”的场景:

jsx
function NotificationPanel({ messages }) {
  return (
    <div className="panel">
      <h3>通知</h3>

      {/* 有消息时渲染徽章 */}
      {messages.length > 0 && (
        <span className="badge">{messages.length}</span>
      )}

      {/* 消息列表 */}
      {messages.map(msg => (
        <div key={msg.id} className="message">{msg.text}</div>
      ))}
    </div>
  );
}

或运算符(||)作为默认值

使用 || 为 falsy 值提供后备内容:

jsx
function UserGreeting({ username }) {
  return <h1>欢迎,{username || '访客'}!</h1>;
}

function ScoreDisplay({ score }) {
  return <p>得分:{score || 0}</p>;
}

完整条件渲染组件示例

jsx
function UserCard({ user, isAdmin, isOnline }) {
  // 方式1:早期返回
  if (!user) {
    return <div className="loading">加载中...</div>;
  }

  // 方式2:三元运算符(布尔状态切换)
  const statusText = isOnline ? '在线' : '离线';
  const statusColor = isOnline ? '#20A020' : '#999';

  // 方式3:逻辑与(可选内容)
  const adminBadge = isAdmin && (
    <span style={{ background: '#D02020', color: 'white', padding: '0.1rem 0.4rem', fontSize: '0.75rem' }}>
      管理员
    </span>
  );

  return (
    <div className="user-card">
      <div className="avatar">
        <img src={user.avatar} alt={user.name} />
        <span
          className="status-dot"
          style={{ background: statusColor }}
        />
      </div>
      <div className="info">
        <h3>
          {user.name}
          {adminBadge}
        </h3>
        <p style={{ color: statusColor }}>{statusText}</p>
        <p>{user.bio || '这个人很懒,什么都没写'}</p>
      </div>
    </div>
  );
}

列表渲染

使用 map 渲染列表

map 是 JavaScript 数组方法,返回一个新数组,非常适合生成 JSX 列表:

jsx
const frameworks = ['React', 'Vue', 'Angular', 'Svelte'];

function FrameworkList() {
  return (
    <ul>
      {frameworks.map((name, index) => (
        <li key={index}>{name}</li>
      ))}
    </ul>
  );
}

renderItem 提取模式

将列表项提取为独立组件,提高可读性和可维护性:

jsx
// 列表项组件
function FrameworkItem({ name, url, description }) {
  return (
    <li className="framework-item">
      <h4>
        <a href={url} target="_blank" rel="noopener noreferrer">
          {name}
        </a>
      </h4>
      <p>{description}</p>
    </li>
  );
}

// 主组件
function FrameworkList() {
  const frameworks = [
    { id: 1, name: 'React', url: 'https://react.dev', description: '用于构建用户界面的 JavaScript 库' },
    { id: 2, name: 'Vue', url: 'https://vuejs.org', description: '渐进式 JavaScript 框架' },
    { id: 3, name: 'Angular', url: 'https://angular.io', description: '企业级应用平台' },
  ];

  return (
    <ul>
      {frameworks.map(framework => (
        <FrameworkItem
          key={framework.id}
          {...framework}
        />
      ))}
    </ul>
  );
}

过滤与映射结合

在 JSX 中同时过滤和映射:

jsx
function FilteredProductList() {
  const [filter, setFilter] = useState('all');

  const products = [
    { id: 1, name: '极简主义设计', category: 'design', price: 199 },
    { id: 2, name: 'React 进阶', category: 'code', price: 299 },
    { id: 3, name: '色彩理论基础', category: 'design', price: 249 },
    { id: 4, name: 'Vue 实战', category: 'code', price: 279 },
  ];

  // 先过滤,后映射
  const visibleProducts = filter === 'all'
    ? products
    : products.filter(p => p.category === filter);

  return (
    <div>
      <div className="filters">
        <button onClick={() => setFilter('all')}>全部</button>
        <button onClick={() => setFilter('design')}>设计</button>
        <button onClick={() => setFilter('code')}>编程</button>
      </div>

      <ul>
        {visibleProducts.map(product => (
          <li key={product.id}>
            {product.name} — ¥{product.price}
          </li>
        ))}
      </ul>

      {visibleProducts.length === 0 && (
        <p>没有找到匹配的产品</p>
      )}
    </div>
  );
}

Key 的作用与最佳实践

什么是 Key

Key 是 React 用于识别列表中每个元素的唯一标识。它帮助 React 在更新时精确定位需要变化的元素:

jsx
// key 帮助 React 识别每个列表项的身份
{todos.map(todo => (
  <TodoItem
    key={todo.id}     // ← 唯一标识
    todo={todo}
    onToggle={toggleTodo}
  />
))}

Key 的工作原理(Diff 算法)

当列表重新渲染时,React 使用 Key 来匹配新旧树中的元素:

plaintext
初始渲染:
  [A, B, C]  key=[1, 2, 3]

更新后:
  [A, D, B]  key=[1, 4, 2]

React 识别:
  - key=1: A 未变化,更新复用
  - key=2: B 位置变化(从索引1到索引2),移动而非销毁重建
  - key=3: 消失(被销毁)
  - key=4: 新增(创建)

Key 的最佳实践

jsx
function KeyBestPractices() {
  const users = [
    { id: 1, name: 'Alice' },   // ✓ 使用数据库 ID
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Carol' },
  ];

  return (
    <div>
      {/* ❌ 错误:使用数组索引作为 key */}
      {users.map((user, index) => (
        <div key={index}>{user.name}</div>  // index 不稳定!
      ))}

      {/* ✓ 正确:使用稳定唯一 ID */}
      {users.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

何时可以使用索引作为 Key

数据不会重新排序、不会添加/删除中间项的情况下,索引是安全的:

jsx
// ✓ 安全:数据顺序固定,只在末尾追加
const staticList = ['React', 'Vue', 'Angular'];
{staticList.map((item, i) => <li key={i}>{item}</li>)}

// ✓ 安全:布尔状态列表,不会改变顺序
const [completed, setCompleted] = useState([false, true, false]);
{completed.map((done, i) => <Checkbox key={i} checked={done} />)}

// ❌ 不安全:数据会被删除、重排
const [items, setItems] = useState([...]);
{items.map(item => <div key={item.id}>{item.name}</div>)}

key 必须是同辈中唯一

Key 在同一层级的兄弟元素之间必须唯一,不需要全局唯一:

jsx
// ✓ 正确:section 内的 items 各自独立
<article>
  <section>
    {posts.map(post => <Post key={post.id} {...post} />)}
  </section>
  <section>
    {products.map(product => <Product key={product.id} {...product} />)}
  </section>
</article>

完整示例:可排序表格

jsx
import { useState } from 'react';

function SortableTable() {
  const [data, setData] = useState([
    { id: 1, name: '极简设计原则', category: '设计', rating: 4.8, views: 12500 },
    { id: 2, name: 'React Hooks 实战', category: '编程', rating: 4.6, views: 8900 },
    { id: 3, name: '色彩心理学', category: '设计', rating: 4.9, views: 15200 },
    { id: 4, name: 'TypeScript 入门', category: '编程', rating: 4.5, views: 7800 },
    { id: 5, name: '字体设计基础', category: '设计', rating: 4.7, views: 6200 },
  ]);

  const [sortKey, setSortKey] = useState('views');
  const [sortDir, setSortDir] = useState('desc');
  const [filter, setFilter] = useState('');

  function handleSort(key) {
    if (key === sortKey) {
      // 同一列:反转方向
      setSortDir(prev => prev === 'asc' ? 'desc' : 'asc');
    } else {
      // 新列:升序
      setSortKey(key);
      setSortDir('asc');
    }
  }

  // 过滤 + 排序
  const filtered = data
    .filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
    .sort((a, b) => {
      const aVal = a[sortKey];
      const bVal = b[sortKey];
      return sortDir === 'asc' ? aVal - bVal : bVal - aVal;
    });

  const SortIcon = ({ column }) => {
    if (column !== sortKey) return <span style={{ opacity: 0.3 }}>↕</span>;
    return <span>{sortDir === 'asc' ? '↑' : '↓'}</span>;
  };

  return (
    <div style={{ maxWidth: 700, margin: '2rem auto' }}>
      <h2>文章管理</h2>

      {/* 筛选 */}
      <div style={{ marginBottom: '1rem' }}>
        <input
          type="text"
          value={filter}
          onChange={e => setFilter(e.target.value)}
          placeholder="搜索文章..."
          style={{ padding: '0.5rem', width: '100%', border: '2px solid #121212', fontSize: '1rem' }}
        />
      </div>

      {/* 表格 */}
      <table style={{ width: '100%', borderCollapse: 'collapse', border: '2px solid #121212' }}>
        <thead>
          <tr style={{ background: '#1040C0', color: 'white' }}>
            {[
              { key: 'name', label: '文章' },
              { key: 'category', label: '分类' },
              { key: 'rating', label: '评分' },
              { key: 'views', label: '浏览' },
            ].map(col => (
              <th
                key={col.key}
                onClick={() => handleSort(col.key)}
                style={{ padding: '0.75rem', textAlign: 'left', cursor: 'pointer', userSelect: 'none' }}
              >
                {col.label} <SortIcon column={col.key} />
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {filtered.map(item => (
            <tr
              key={item.id}
              style={{ borderBottom: '1px solid #ddd', background: 'white' }}
            >
              <td style={{ padding: '0.75rem' }}>{item.name}</td>
              <td>
                <span style={{
                  display: 'inline-block',
                  padding: '0.2rem 0.5rem',
                  fontSize: '0.75rem',
                  border: '1px solid',
                  borderColor: item.category === '设计' ? '#1040C0' : '#D02020',
                  color: item.category === '设计' ? '#1040C0' : '#D02020',
                }}>
                  {item.category}
                </span>
              </td>
              <td style={{ color: '#F0C020', fontWeight: 'bold' }}>
                ★ {item.rating}
              </td>
              <td>{item.views.toLocaleString()}</td>
            </tr>
          ))}
        </tbody>
      </table>

      {/* 空状态 */}
      {filtered.length === 0 && (
        <div style={{ textAlign: 'center', padding: '2rem', color: '#999' }}>
          没有找到匹配的文章
        </div>
      )}

      <p style={{ color: '#666', fontSize: '0.875rem', marginTop: '0.5rem' }}>
        共 {filtered.length} 篇文章
      </p>
    </div>
  );
}

export default SortableTable;

小结

  • 条件渲染:早期返回、&& 运算符、三元运算符、|| 默认值,各有其适用场景
  • Falsy 值陷阱:注意 0''false 使用 && 时的边界情况
  • 列表渲染Array.map() 是生成列表的标准方式,每个元素需要 key 属性
  • Key 的作用:帮助 React 识别每个列表项,支持高效的更新和 DOM 复用
  • Key 最佳实践:始终使用稳定唯一 ID,避免使用数组索引(除非数据绝对不变)
  • 过滤与映射:可以先过滤后映射,也可以在 map 内部用三元处理空状态

条件渲染和列表渲染是 React 动态 UI 的基石。掌握这些模式后,你已经具备了构建完整交互式 React 应用的能力。接下来可以进入进阶技能部分,深入学习 useEffect 等 Hooks 的高级用法。

#react #条件渲染 #列表渲染 #key #入门

评论

A

Written by

AI-Writer

Related Articles

react
#4

事件处理与绑定

深入理解 React 的合成事件系统、事件绑定方式、传参模式以及事件委托机制,掌握各类交互事件的处理方法

Read More
react
#10

表单处理

掌握 React 中的表单处理模式,包括受控与非受控组件、表单验证,以及 React Hook Form 的使用

Read More
react
#2

组件与 Props

掌握 React 函数组件的定义与使用,理解 Props 的传递机制、children 属性、默认值设置,以及良好的组件设计原则

Read More