Skip to content

React 条件和列表渲染

概述

在 React 中,我们经常需要根据不同的条件显示不同的内容,或者渲染动态的列表数据。本章将学习如何在 React 中进行条件渲染和列表渲染,掌握各种渲染技巧和最佳实践。

🔀 条件渲染

基础条件渲染

jsx
function BasicConditionalRendering() {
  const [isLoggedIn, setIsLoggedIn] = React.useState(false);
  const [user, setUser] = React.useState(null);
  
  const login = () => {
    setUser({ name: '张三', role: 'admin' });
    setIsLoggedIn(true);
  };
  
  const logout = () => {
    setUser(null);
    setIsLoggedIn(false);
  };
  
  // 方式1:使用 if 语句
  if (!isLoggedIn) {
    return (
      <div>
        <h2>请登录</h2>
        <button onClick={login}>登录</button>
      </div>
    );
  }
  
  return (
    <div>
      <h2>欢迎,{user.name}!</h2>
      <button onClick={logout}>登出</button>
    </div>
  );
}

三元运算符

jsx
function TernaryOperator() {
  const [loading, setLoading] = React.useState(false);
  const [data, setData] = React.useState(null);
  const [error, setError] = React.useState(null);
  
  const fetchData = async () => {
    setLoading(true);
    setError(null);
    
    try {
      // 模拟 API 调用
      await new Promise(resolve => setTimeout(resolve, 2000));
      setData({ message: '数据加载成功!' });
    } catch (err) {
      setError('加载失败');
    } finally {
      setLoading(false);
    }
  };
  
  return (
    <div>
      <h2>数据获取示例</h2>
      <button onClick={fetchData} disabled={loading}>
        {loading ? '加载中...' : '获取数据'}
      </button>
      
      {/* 三元运算符嵌套 */}
      {loading ? (
        <div>正在加载数据...</div>
      ) : error ? (
        <div style={{ color: 'red' }}>错误: {error}</div>
      ) : data ? (
        <div style={{ color: 'green' }}>{data.message}</div>
      ) : (
        <div>点击按钮获取数据</div>
      )}
    </div>
  );
}

逻辑与运算符

jsx
function LogicalAndOperator() {
  const [showWarning, setShowWarning] = React.useState(false);
  const [notifications, setNotifications] = React.useState([]);
  const [userRole, setUserRole] = React.useState('user');
  
  const addNotification = () => {
    const newNotification = {
      id: Date.now(),
      message: `通知 ${notifications.length + 1}`,
      timestamp: new Date().toLocaleTimeString()
    };
    setNotifications([...notifications, newNotification]);
  };
  
  return (
    <div>
      <h2>条件显示组件</h2>
      
      <div>
        <button onClick={() => setShowWarning(!showWarning)}>
          切换警告显示
        </button>
        <button onClick={addNotification}>添加通知</button>
        <button onClick={() => setUserRole(userRole === 'admin' ? 'user' : 'admin')}>
          切换用户角色: {userRole}
        </button>
      </div>
      
      {/* 使用 && 运算符 */}
      {showWarning && (
        <div style={{ backgroundColor: '#fff3cd', padding: '10px', margin: '10px 0' }}>
          ⚠️ 这是一个警告消息
        </div>
      )}
      
      {/* 条件显示通知 */}
      {notifications.length > 0 && (
        <div>
          <h3>通知列表 ({notifications.length})</h3>
          {notifications.map(notification => (
            <div key={notification.id} style={{ padding: '5px', border: '1px solid #ddd' }}>
              [{notification.timestamp}] {notification.message}
            </div>
          ))}
        </div>
      )}
      
      {/* 管理员专用功能 */}
      {userRole === 'admin' && (
        <div style={{ backgroundColor: '#d4edda', padding: '10px', margin: '10px 0' }}>
          <h3>管理员面板</h3>
          <button>用户管理</button>
          <button>系统设置</button>
        </div>
      )}
    </div>
  );
}

Switch 语句模拟

jsx
function SwitchLikeRendering() {
  const [status, setStatus] = React.useState('loading');
  const [userType, setUserType] = React.useState('guest');
  
  // 使用对象映射模拟 switch
  const statusComponents = {
    loading: <div>⏳ 加载中...</div>,
    success: <div style={{ color: 'green' }}>✅ 操作成功</div>,
    error: <div style={{ color: 'red' }}>❌ 操作失败</div>,
    idle: <div>等待操作</div>
  };
  
  // 使用函数模拟 switch
  const renderUserInterface = () => {
    switch (userType) {
      case 'admin':
        return (
          <div style={{ backgroundColor: '#e7f3ff' }}>
            <h3>管理员界面</h3>
            <button>用户管理</button>
            <button>系统设置</button>
            <button>数据统计</button>
          </div>
        );
      case 'vip':
        return (
          <div style={{ backgroundColor: '#fff7e6' }}>
            <h3>VIP 用户界面</h3>
            <button>专属服务</button>
            <button>高级功能</button>
          </div>
        );
      case 'user':
        return (
          <div style={{ backgroundColor: '#f6fff7' }}>
            <h3>普通用户界面</h3>
            <button>基础功能</button>
          </div>
        );
      default:
        return (
          <div>
            <h3>访客界面</h3>
            <button>注册</button>
            <button>登录</button>
          </div>
        );
    }
  };
  
  return (
    <div>
      <h2>复杂条件渲染</h2>
      
      <div>
        <label>状态: </label>
        <select value={status} onChange={(e) => setStatus(e.target.value)}>
          <option value="loading">加载中</option>
          <option value="success">成功</option>
          <option value="error">错误</option>
          <option value="idle">空闲</option>
        </select>
      </div>
      
      <div>
        <label>用户类型: </label>
        <select value={userType} onChange={(e) => setUserType(e.target.value)}>
          <option value="guest">访客</option>
          <option value="user">用户</option>
          <option value="vip">VIP</option>
          <option value="admin">管理员</option>
        </select>
      </div>
      
      <div style={{ margin: '20px 0' }}>
        <h3>当前状态:</h3>
        {statusComponents[status]}
      </div>
      
      <div>
        {renderUserInterface()}
      </div>
    </div>
  );
}

📋 列表渲染

基础列表渲染

jsx
function BasicListRendering() {
  const [items, setItems] = React.useState([
    { id: 1, name: '苹果', price: 5.99, category: '水果' },
    { id: 2, name: '香蕉', price: 3.99, category: '水果' },
    { id: 3, name: '牛奶', price: 12.99, category: '乳制品' },
    { id: 4, name: '面包', price: 8.99, category: '主食' }
  ]);
  
  const [filter, setFilter] = React.useState('all');
  
  const filteredItems = items.filter(item => 
    filter === 'all' || item.category === filter
  );
  
  const categories = ['all', ...new Set(items.map(item => item.category))];
  
  return (
    <div>
      <h2>商品列表</h2>
      
      {/* 过滤器 */}
      <div style={{ marginBottom: '20px' }}>
        {categories.map(category => (
          <button
            key={category}
            onClick={() => setFilter(category)}
            style={{
              margin: '0 5px',
              backgroundColor: filter === category ? '#007bff' : '#f8f9fa',
              color: filter === category ? 'white' : 'black'
            }}
          >
            {category === 'all' ? '全部' : category}
          </button>
        ))}
      </div>
      
      {/* 商品列表 */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '10px' }}>
        {filteredItems.map(item => (
          <div
            key={item.id}
            style={{
              border: '1px solid #ddd',
              borderRadius: '5px',
              padding: '15px',
              backgroundColor: '#f8f9fa'
            }}
          >
            <h3>{item.name}</h3>
            <p>分类: {item.category}</p>
            <p style={{ color: '#007bff', fontSize: '18px', fontWeight: 'bold' }}>
              ¥{item.price}
            </p>
          </div>
        ))}
      </div>
      
      {filteredItems.length === 0 && (
        <div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
          没有找到符合条件的商品
        </div>
      )}
    </div>
  );
}

动态列表操作

jsx
function DynamicListOperations() {
  const [todos, setTodos] = React.useState([
    { id: 1, text: '学习 React', completed: false, priority: 'high' },
    { id: 2, text: '完成项目', completed: true, priority: 'medium' },
    { id: 3, text: '写文档', completed: false, priority: 'low' }
  ]);
  
  const [newTodo, setNewTodo] = React.useState('');
  const [sortBy, setSortBy] = React.useState('id');
  
  const addTodo = () => {
    if (newTodo.trim()) {
      const todo = {
        id: Date.now(),
        text: newTodo,
        completed: false,
        priority: 'medium'
      };
      setTodos([...todos, todo]);
      setNewTodo('');
    }
  };
  
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };
  
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  
  const changePriority = (id, priority) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, priority } : todo
    ));
  };
  
  // 排序逻辑
  const sortedTodos = [...todos].sort((a, b) => {
    switch (sortBy) {
      case 'text':
        return a.text.localeCompare(b.text);
      case 'completed':
        return a.completed - b.completed;
      case 'priority':
        const priorityOrder = { high: 3, medium: 2, low: 1 };
        return priorityOrder[b.priority] - priorityOrder[a.priority];
      default:
        return a.id - b.id;
    }
  });
  
  const getPriorityColor = (priority) => {
    switch (priority) {
      case 'high': return '#dc3545';
      case 'medium': return '#ffc107';
      case 'low': return '#28a745';
      default: return '#6c757d';
    }
  };
  
  return (
    <div>
      <h2>待办事项管理</h2>
      
      {/* 添加新任务 */}
      <div style={{ marginBottom: '20px' }}>
        <input
          type="text"
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && addTodo()}
          placeholder="输入新任务..."
          style={{ padding: '8px', marginRight: '10px', width: '200px' }}
        />
        <button onClick={addTodo}>添加</button>
      </div>
      
      {/* 排序选项 */}
      <div style={{ marginBottom: '20px' }}>
        <label>排序方式: </label>
        <select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
          <option value="id">默认</option>
          <option value="text">按名称</option>
          <option value="completed">按状态</option>
          <option value="priority">按优先级</option>
        </select>
      </div>
      
      {/* 任务列表 */}
      <div>
        {sortedTodos.map(todo => (
          <div
            key={todo.id}
            style={{
              display: 'flex',
              alignItems: 'center',
              padding: '10px',
              margin: '5px 0',
              border: '1px solid #ddd',
              borderRadius: '5px',
              backgroundColor: todo.completed ? '#f8f9fa' : 'white'
            }}
          >
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
              style={{ marginRight: '10px' }}
            />
            
            <span
              style={{
                flex: 1,
                textDecoration: todo.completed ? 'line-through' : 'none',
                color: todo.completed ? '#6c757d' : 'black'
              }}
            >
              {todo.text}
            </span>
            
            <select
              value={todo.priority}
              onChange={(e) => changePriority(todo.id, e.target.value)}
              style={{
                marginRight: '10px',
                color: getPriorityColor(todo.priority)
              }}
            >
              <option value="low">低</option>
              <option value="medium">中</option>
              <option value="high">高</option>
            </select>
            
            <button
              onClick={() => deleteTodo(todo.id)}
              style={{ color: 'red', background: 'none', border: 'none' }}
            >
              删除
            </button>
          </div>
        ))}
      </div>
      
      {todos.length === 0 && (
        <div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
          暂无任务,添加一个开始吧!
        </div>
      )}
      
      {/* 统计信息 */}
      <div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#f8f9fa' }}>
        <p>总任务: {todos.length}</p>
        <p>已完成: {todos.filter(t => t.completed).length}</p>
        <p>未完成: {todos.filter(t => !t.completed).length}</p>
      </div>
    </div>
  );
}

Key 属性的重要性

jsx
function KeyImportance() {
  const [items, setItems] = React.useState([
    { id: 'a', name: '项目 A', count: 0 },
    { id: 'b', name: '项目 B', count: 0 },
    { id: 'c', name: '项目 C', count: 0 }
  ]);
  
  const addItemAtStart = () => {
    const newItem = {
      id: Math.random().toString(36).substr(2, 9),
      name: `项目 ${String.fromCharCode(65 + items.length)}`,
      count: 0
    };
    setItems([newItem, ...items]);
  };
  
  const updateCount = (id, delta) => {
    setItems(items.map(item =>
      item.id === id ? { ...item, count: item.count + delta } : item
    ));
  };
  
  const removeItem = (id) => {
    setItems(items.filter(item => item.id !== id));
  };
  
  return (
    <div>
      <h2>Key 属性演示</h2>
      <button onClick={addItemAtStart} style={{ marginBottom: '20px' }}>
        在开头添加项目
      </button>
      
      <div style={{ display: 'flex', gap: '20px' }}>
        {/* 正确使用 key */}
        <div>
          <h3>✅ 正确:使用唯一 ID 作为 key</h3>
          {items.map(item => (
            <div key={item.id} style={{ border: '1px solid #ddd', padding: '10px', margin: '5px 0' }}>
              <div>{item.name}</div>
              <div>
                <input
                  type="number"
                  value={item.count}
                  onChange={(e) => updateCount(item.id, parseInt(e.target.value) - item.count)}
                  style={{ width: '60px', marginRight: '10px' }}
                />
                <button onClick={() => updateCount(item.id, 1)}>+</button>
                <button onClick={() => updateCount(item.id, -1)}>-</button>
                <button onClick={() => removeItem(item.id)} style={{ marginLeft: '10px', color: 'red' }}>
                  删除
                </button>
              </div>
            </div>
          ))}
        </div>
        
        {/* 错误使用 key */}
        <div>
          <h3>❌ 错误:使用索引作为 key</h3>
          {items.map((item, index) => (
            <div key={index} style={{ border: '1px solid #ddd', padding: '10px', margin: '5px 0' }}>
              <div>{item.name}</div>
              <div>
                <input
                  type="number"
                  value={item.count}
                  onChange={(e) => updateCount(item.id, parseInt(e.target.value) - item.count)}
                  style={{ width: '60px', marginRight: '10px' }}
                />
                <button onClick={() => updateCount(item.id, 1)}>+</button>
                <button onClick={() => updateCount(item.id, -1)}>-</button>
                <button onClick={() => removeItem(item.id)} style={{ marginLeft: '10px', color: 'red' }}>
                  删除
                </button>
              </div>
            </div>
          ))}
        </div>
      </div>
      
      <div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#fff3cd' }}>
        <strong>注意:</strong> 在左侧输入一些数字,然后点击"在开头添加项目",
        观察两列的输入值变化。使用索引作为 key 会导致输入状态错乱。
      </div>
    </div>
  );
}

🎯 高级渲染技巧

渲染优化

jsx
const MemoizedItem = React.memo(function Item({ item, onUpdate, onDelete }) {
  console.log('渲染:', item.name);
  
  return (
    <div style={{ padding: '10px', border: '1px solid #ddd', margin: '5px 0' }}>
      <span>{item.name} - {item.value}</span>
      <button onClick={() => onUpdate(item.id)}>更新</button>
      <button onClick={() => onDelete(item.id)}>删除</button>
    </div>
  );
});

function OptimizedRendering() {
  const [items, setItems] = React.useState([
    { id: 1, name: '项目 1', value: 0 },
    { id: 2, name: '项目 2', value: 0 },
    { id: 3, name: '项目 3', value: 0 }
  ]);
  
  const [counter, setCounter] = React.useState(0);
  
  const updateItem = React.useCallback((id) => {
    setItems(items => items.map(item =>
      item.id === id ? { ...item, value: item.value + 1 } : item
    ));
  }, []);
  
  const deleteItem = React.useCallback((id) => {
    setItems(items => items.filter(item => item.id !== id));
  }, []);
  
  return (
    <div>
      <h2>渲染优化演示</h2>
      <p>计数器: {counter}</p>
      <button onClick={() => setCounter(counter + 1)}>
        增加计数器(不影响列表项渲染)
      </button>
      
      <div>
        {items.map(item => (
          <MemoizedItem
            key={item.id}
            item={item}
            onUpdate={updateItem}
            onDelete={deleteItem}
          />
        ))}
      </div>
    </div>
  );
}

📝 本章小结

通过本章学习,你应该掌握了:

条件渲染技巧

  • ✅ if 语句、三元运算符、逻辑与运算符
  • ✅ 复杂条件的处理策略
  • ✅ 状态驱动的 UI 变化
  • ✅ 错误边界和加载状态

列表渲染技巧

  • ✅ map() 方法渲染数组
  • ✅ Key 属性的正确使用
  • ✅ 动态列表的增删改查
  • ✅ 列表排序和过滤

最佳实践

  1. Key 的重要性:使用唯一且稳定的标识符
  2. 性能优化:使用 React.memo 避免不必要的渲染
  3. 条件复杂性:合理拆分复杂的条件逻辑
  4. 状态管理:保持状态结构简单清晰
  5. 用户体验:提供加载状态和空状态提示

继续学习下一章 - React 生命周期

本站内容仅供学习和研究使用。