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>© 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 属性
- ✅ 空值和错误状态处理
- ✅ 性能优化策略
最佳实践
- 使用正确的 Key:确保列表渲染性能
- 合理的条件渲染:避免不必要的元素创建
- 性能优化:使用 memo 和 useMemo
- 错误边界:处理渲染错误
- 懒加载:按需加载组件
重要原则
- React 元素是不可变的
- 每次更新都会创建新的元素树
- React 使用 Diff 算法优化更新
- Key 属性帮助 React 识别元素变化
继续学习:下一章 - React 事件处理