Skip to content

React 元素渲染

概述

React 元素是构建 React 应用的最小单位。本章将深入了解 React 元素的创建、渲染机制、虚拟 DOM 的工作原理,以及如何高效地更新用户界面。

🧩 什么是 React 元素

React 元素基础

jsx
// React 元素是描述你想在屏幕上看到什么的对象
const element = <h1>Hello, World!</h1>;

// 等价于(使用 React.createElement)
const element = React.createElement(
  'h1',           // 标签名
  null,           // 属性
  'Hello, World!' // 子元素
);

// React 元素是不可变的对象
console.log(element);
// 输出:
// {
//   type: 'h1',
//   props: {
//     children: 'Hello, World!'
//   },
//   key: null,
//   ref: null,
//   ...
// }

元素 vs 组件

jsx
// React 元素 - 描述 DOM 节点的对象
const divElement = <div>This is an element</div>;

// React 组件 - 返回元素的函数或类
function Welcome() {
  return <h1>This is a component</h1>;
}

// 组件元素 - 描述组件实例的对象
const componentElement = <Welcome />;

🎨 创建 React 元素

JSX 创建元素

jsx
// 简单元素
const heading = <h1>标题</h1>;
const paragraph = <p>这是一个段落</p>;

// 带属性的元素
const link = <a href="https://react.dev" target="_blank">React 官网</a>;
const image = <img src="/logo.png" alt="Logo" width="100" />;

// 带样式的元素
const styledDiv = (
  <div 
    style={{ 
      backgroundColor: 'blue', 
      color: 'white', 
      padding: '20px' 
    }}
  >
    样式化的 div
  </div>
);

// 复杂元素结构
const card = (
  <div className="card">
    <img src="/avatar.jpg" alt="Avatar" className="card-image" />
    <div className="card-content">
      <h3 className="card-title">用户名</h3>
      <p className="card-description">这是用户描述</p>
      <button className="card-button">查看详情</button>
    </div>
  </div>
);

React.createElement 创建元素

jsx
// 使用 React.createElement 创建元素
const elementWithoutJSX = React.createElement(
  'div',                          // 元素类型
  { className: 'container' },     // 属性对象
  React.createElement(            // 子元素
    'h1',
    { style: { color: 'red' } },
    '标题'
  ),
  React.createElement(
    'p',
    null,
    '这是一个段落'
  )
);

// 等价的 JSX
const elementWithJSX = (
  <div className="container">
    <h1 style={{ color: 'red' }}>标题</h1>
    <p>这是一个段落</p>
  </div>
);

🚀 渲染元素到 DOM

基础渲染

jsx
import React from 'react';
import ReactDOM from 'react-dom/client';

// 创建元素
const element = <h1>Hello, React!</h1>;

// 获取 DOM 容器
const container = document.getElementById('root');

// 创建 React 根
const root = ReactDOM.createRoot(container);

// 渲染元素
root.render(element);

渲染复杂元素

jsx
function App() {
  const user = {
    name: '张三',
    email: 'zhangsan@example.com',
    avatar: '/avatars/zhangsan.jpg'
  };
  
  const element = (
    <div className="app">
      <header className="app-header">
        <h1>欢迎使用我们的应用</h1>
        <nav>
          <a href="/home">首页</a>
          <a href="/about">关于</a>
          <a href="/contact">联系</a>
        </nav>
      </header>
      
      <main className="app-main">
        <div className="user-profile">
          <img src={user.avatar} alt={user.name} />
          <h2>{user.name}</h2>
          <p>{user.email}</p>
        </div>
        
        <section className="content">
          <h3>主要内容</h3>
          <p>这里是应用的主要内容区域。</p>
        </section>
      </main>
      
      <footer className="app-footer">
        <p>&copy; 2024 我的应用. 保留所有权利。</p>
      </footer>
    </div>
  );
  
  return element;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

🔄 更新已渲染的元素

元素不可变性

jsx
// React 元素是不可变的
const element1 = <h1>第一次渲染</h1>;
const element2 = <h1>第二次渲染</h1>;

// 不能修改已创建的元素
// element1.props.children = '修改后的内容'; // 这是错误的!

// 要更新 UI,需要创建新元素并重新渲染
const root = ReactDOM.createRoot(document.getElementById('root'));

// 第一次渲染
root.render(element1);

// 更新渲染(创建新元素)
setTimeout(() => {
  root.render(element2);
}, 2000);

实时更新示例

jsx
function Clock() {
  const [time, setTime] = React.useState(new Date());
  
  // 每秒更新时间
  React.useEffect(() => {
    const timer = setInterval(() => {
      setTime(new Date());
    }, 1000);
    
    // 清理定时器
    return () => clearInterval(timer);
  }, []);
  
  // 每次状态更新都会创建新的元素
  return (
    <div className="clock">
      <h2>当前时间</h2>
      <div className="time-display">
        {time.toLocaleTimeString()}
      </div>
      <div className="date-display">
        {time.toLocaleDateString()}
      </div>
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>实时时钟应用</h1>
      <Clock />
    </div>
  );
}

🌳 虚拟 DOM 概念

虚拟 DOM 工作原理

jsx
// 虚拟 DOM 是 React 元素的 JavaScript 表示
const virtualDOM = {
  type: 'div',
  props: {
    className: 'container',
    children: [
      {
        type: 'h1',
        props: {
          children: '标题'
        }
      },
      {
        type: 'p',
        props: {
          children: '段落内容'
        }
      }
    ]
  }
};

// React 使用虚拟 DOM 来计算最小的更新
function VirtualDOMExample() {
  const [showDetails, setShowDetails] = React.useState(false);
  
  return (
    <div className="container">
      <h1>用户信息</h1>
      <button onClick={() => setShowDetails(!showDetails)}>
        {showDetails ? '隐藏' : '显示'}详情
      </button>
      {showDetails && (
        <div className="details">
          <p>姓名:张三</p>
          <p>年龄:25</p>
          <p>职业:程序员</p>
        </div>
      )}
    </div>
  );
}

// React 只会更新变化的部分,而不是重新渲染整个组件

Diff 算法演示

jsx
function DiffExample() {
  const [items, setItems] = React.useState([
    { id: 1, name: '苹果' },
    { id: 2, name: '香蕉' },
    { id: 3, name: '橙子' }
  ]);
  
  const addItem = () => {
    const newItem = {
      id: Date.now(),
      name: `新水果 ${items.length + 1}`
    };
    setItems([...items, newItem]);
  };
  
  const removeItem = (id) => {
    setItems(items.filter(item => item.id !== id));
  };
  
  return (
    <div>
      <h2>水果列表</h2>
      <button onClick={addItem}>添加水果</button>
      
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.name}
            <button onClick={() => removeItem(item.id)}>
              删除
            </button>
          </li>
        ))}
      </ul>
      
      <p>总数:{items.length}</p>
    </div>
  );
}

// React 使用 key 属性来识别哪些元素发生了变化
// 这样可以更高效地更新 DOM

🎯 条件渲染元素

基础条件渲染

jsx
function ConditionalRendering() {
  const [isLoggedIn, setIsLoggedIn] = React.useState(false);
  const [userRole, setUserRole] = React.useState('guest');
  
  // 条件渲染不同的元素
  let welcomeElement;
  if (isLoggedIn) {
    welcomeElement = <h1>欢迎回来!</h1>;
  } else {
    welcomeElement = <h1>请先登录</h1>;
  }
  
  return (
    <div>
      {welcomeElement}
      
      {/* 使用三元运算符 */}
      {isLoggedIn ? (
        <div className="user-dashboard">
          <p>您已成功登录</p>
          <button onClick={() => setIsLoggedIn(false)}>
            登出
          </button>
        </div>
      ) : (
        <div className="login-form">
          <p>请登录以继续</p>
          <button onClick={() => setIsLoggedIn(true)}>
            登录
          </button>
        </div>
      )}
      
      {/* 使用逻辑与运算符 */}
      {isLoggedIn && userRole === 'admin' && (
        <div className="admin-panel">
          <h3>管理员面板</h3>
          <button>管理用户</button>
          <button>系统设置</button>
        </div>
      )}
      
      {/* 复杂条件渲染 */}
      {(() => {
        switch (userRole) {
          case 'admin':
            return <div>管理员界面</div>;
          case 'user':
            return <div>用户界面</div>;
          case 'guest':
          default:
            return <div>访客界面</div>;
        }
      })()}
    </div>
  );
}

空值处理

jsx
function NullHandling() {
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);
  
  React.useEffect(() => {
    // 模拟数据获取
    setTimeout(() => {
      try {
        // 随机成功或失败
        if (Math.random() > 0.5) {
          setData({ message: '数据加载成功!' });
        } else {
          throw new Error('数据加载失败');
        }
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }, 2000);
  }, []);
  
  // 不同状态返回不同元素
  if (loading) {
    return (
      <div className="loading">
        <p>正在加载...</p>
      </div>
    );
  }
  
  if (error) {
    return (
      <div className="error">
        <p>错误:{error}</p>
        <button onClick={() => window.location.reload()}>
          重试
        </button>
      </div>
    );
  }
  
  if (!data) {
    return (
      <div className="no-data">
        <p>暂无数据</p>
      </div>
    );
  }
  
  return (
    <div className="success">
      <p>{data.message}</p>
    </div>
  );
}

📋 列表渲染元素

基础列表渲染

jsx
function ListRendering() {
  const fruits = ['苹果', '香蕉', '橙子', '葡萄', '草莓'];
  
  const users = [
    { id: 1, name: '张三', email: 'zhangsan@example.com' },
    { id: 2, name: '李四', email: 'lisi@example.com' },
    { id: 3, name: '王五', email: 'wangwu@example.com' }
  ];
  
  return (
    <div>
      {/* 简单列表 */}
      <h2>水果列表</h2>
      <ul>
        {fruits.map((fruit, index) => (
          <li key={index}>{fruit}</li>
        ))}
      </ul>
      
      {/* 复杂列表 */}
      <h2>用户列表</h2>
      <div className="user-list">
        {users.map(user => (
          <div key={user.id} className="user-card">
            <h3>{user.name}</h3>
            <p>{user.email}</p>
            <button>查看详情</button>
          </div>
        ))}
      </div>
    </div>
  );
}

Key 属性的重要性

jsx
function KeyImportance() {
  const [items, setItems] = React.useState([
    { id: 1, name: '项目 1', count: 0 },
    { id: 2, name: '项目 2', count: 0 },
    { id: 3, name: '项目 3', count: 0 }
  ]);
  
  const addItem = () => {
    const newItem = {
      id: Date.now(),
      name: `项目 ${items.length + 1}`,
      count: 0
    };
    setItems([newItem, ...items]); // 在开头添加
  };
  
  const updateCount = (id, newCount) => {
    setItems(items.map(item =>
      item.id === id ? { ...item, count: newCount } : item
    ));
  };
  
  return (
    <div>
      <h2>Key 属性演示</h2>
      <button onClick={addItem}>在开头添加项目</button>
      
      <div className="items-container">
        {items.map(item => (
          <div key={item.id} className="item">
            <h3>{item.name}</h3>
            <div>
              <span>计数: {item.count}</span>
              <button onClick={() => updateCount(item.id, item.count + 1)}>
                +1
              </button>
              <button onClick={() => updateCount(item.id, item.count - 1)}>
                -1
              </button>
            </div>
          </div>
        ))}
      </div>
      
      {/* 错误示例:使用索引作为 key */}
      <h3>错误示例(不要这样做):</h3>
      <div className="bad-example">
        {items.map((item, index) => (
          <div key={index} className="item">
            {/* 使用索引作为 key 可能导致渲染问题 */}
            <span>{item.name}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

⚡ 性能优化

React.memo 优化渲染

jsx
// 被优化的子组件
const ExpensiveComponent = React.memo(function ExpensiveComponent({ value, onUpdate }) {
  console.log('ExpensiveComponent 渲染', value);
  
  // 模拟昂贵的计算
  const expensiveValue = React.useMemo(() => {
    console.log('执行昂贵计算');
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += Math.random();
    }
    return result;
  }, [value]);
  
  return (
    <div className="expensive-component">
      <h3>昂贵组件</h3>
      <p>值: {value}</p>
      <p>计算结果: {expensiveValue.toFixed(2)}</p>
      <button onClick={() => onUpdate(value + 1)}>
        更新值
      </button>
    </div>
  );
});

function PerformanceExample() {
  const [count, setCount] = React.useState(0);
  const [otherState, setOtherState] = React.useState(0);
  
  // 使用 useCallback 优化回调函数
  const handleUpdate = React.useCallback((newValue) => {
    setCount(newValue);
  }, []);
  
  return (
    <div>
      <h2>性能优化示例</h2>
      <div>
        <p>计数: {count}</p>
        <p>其他状态: {otherState}</p>
        
        <button onClick={() => setCount(count + 1)}>
          增加计数
        </button>
        <button onClick={() => setOtherState(otherState + 1)}>
          增加其他状态
        </button>
      </div>
      
      {/* 只有当 count 改变时才重新渲染 */}
      <ExpensiveComponent 
        value={count} 
        onUpdate={handleUpdate}
      />
    </div>
  );
}

懒加载元素

jsx
const LazyComponent = React.lazy(() => import('./LazyComponent'));

function LazyRenderingExample() {
  const [showLazy, setShowLazy] = React.useState(false);
  
  return (
    <div>
      <h2>懒加载示例</h2>
      <button onClick={() => setShowLazy(!showLazy)}>
        {showLazy ? '隐藏' : '显示'}懒加载组件
      </button>
      
      {showLazy && (
        <React.Suspense fallback={<div>加载中...</div>}>
          <LazyComponent />
        </React.Suspense>
      )}
    </div>
  );
}

📝 本章小结

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

核心概念

  • ✅ React 元素的本质和特点
  • ✅ 虚拟 DOM 的工作原理
  • ✅ 元素的创建和渲染机制
  • ✅ 元素更新的不可变性

渲染技巧

  • ✅ 条件渲染的多种方式
  • ✅ 列表渲染和 Key 属性
  • ✅ 空值和错误状态处理
  • ✅ 性能优化策略

最佳实践

  1. 使用正确的 Key:确保列表渲染性能
  2. 合理的条件渲染:避免不必要的元素创建
  3. 性能优化:使用 memo 和 useMemo
  4. 错误边界:处理渲染错误
  5. 懒加载:按需加载组件

重要原则

  • React 元素是不可变的
  • 每次更新都会创建新的元素树
  • React 使用 Diff 算法优化更新
  • Key 属性帮助 React 识别元素变化

继续学习下一章 - React 事件处理

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