pin_drop当前位置:知识文库 ❯ 图文
框架集成:03 React JSX与HT - 详细教程与实战指南
一、教程简介
JSX是React的核心语法扩展,它允许开发者在JavaScript中编写类似HTML的标记语法。JSX看起来像HTML,但本质上是JavaScript表达式,编译后会变成React.createElement调用。理解JSX与标准HTML的差异是掌握React开发的关键。本教程将详细介绍JSX的语法规则、与HTML的区别、表达式嵌入、条件渲染、列表渲染、Fragment以及JSX的编译原理。
二、核心概念
JSX语法规则
JSX是JavaScript XML的缩写,它遵循以下核心规则:
-
必须有一个根元素:JSX表达式必须包裹在一个根元素中
-
标签必须闭合:所有标签必须正确闭合,包括自闭合标签
-
属性名使用驼峰命名:如className而非class,onClick而非onclick
-
JSX是表达式:可以在if语句、for循环中使用,可以赋值给变量,可以作为参数传递
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JSX语法规则演示</title>
<style>
body { font-family: -apple-system, sans-serif; padding: 20px; max-width: 900px; margin: 0 auto; background: #f5f5f5; }
.panel { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin: 15px 0; }
pre { background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 8px; overflow-x: auto; font-size: 13px; }
.rule { border-left: 3px solid #61dafb; padding-left: 15px; margin: 15px 0; }
.rule h3 { color: #61dafb; margin-top: 0; }
.correct { color: #27ae60; }
.incorrect { color: #e74c3c; text-decoration: line-through; }
#jsx-demo { border: 2px dashed #61dafb; padding: 20px; border-radius: 8px; margin: 15px 0; }
</style>
</head>
<body>
<h1>JSX语法规则演示</h1>
<div class="panel">
<div class="rule">
<h3>规则1:必须有一个根元素</h3>
<pre><span class="incorrect">// 错误:多个根元素</span>
return (
<h1>标题</h1>
<p>段落</p>
);
<span class="correct">// 正确:单个根元素</span>
return (
<div>
<h1>标题</h1>
<p>段落</p>
</div>
);
<span class="correct">// 或使用Fragment</span>
return (
<>
<h1>标题</h1>
<p>段落</p>
</>
);</pre>
</div>
<div class="rule">
<h3>规则2:标签必须闭合</h3>
<pre><span class="incorrect">// 错误:未闭合标签</span>
<img src="photo.jpg">
<br>
<input type="text">
<span class="correct">// 正确:自闭合标签</span>
<img src="photo.jpg" />
<br />
<input type="text" /></pre>
</div>
<div class="rule">
<h3>规则3:属性名使用驼峰命名</h3>
<pre><span class="incorrect">// 错误:HTML属性名</span>
<div class="container" tabindex="1">
<label for="name">
<div style="color: red;">
<span class="correct">// 正确:JSX属性名</span>
<div className="container" tabIndex={1}>
<label htmlFor="name">
<div style={{ color: 'red' }}></pre>
</div>
</div>
<div class="panel">
<h2>JSX渲染演示</h2>
<div id="jsx-demo"></div>
</div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
function JSXDemo() {
return (
<div>
<h3>JSX渲染成功!</h3>
<p className="demo-text" style={{ color: '#667eea', fontWeight: 'bold' }}>
这段文字通过JSX渲染,使用了className和style对象
</p>
<img
src="https://picsum.photos/seed/jsx/200/100"
alt="JSX演示图片"
style={{ borderRadius: '8px' }}
/>
<br />
<label htmlFor="demo-input">输入框:</label>
<input id="demo-input" type="text" placeholder="自闭合标签" />
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('jsx-demo'));
root.render(<JSXDemo />);
</script>
</body>
</html>JSX与HTML的区别
JSX虽然看起来像HTML,但有很多关键区别:
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JSX与HTML的区别</title>
<style>
body { font-family: -apple-system, sans-serif; padding: 20px; max-width: 1000px; margin: 0 auto; background: #f5f5f5; }
.panel { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin: 15px 0; }
table { width: 100%; border-collapse: collapse; margin: 10px 0; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background: #61dafb; color: #000; }
.html-col { background: #fff3cd; }
.jsx-col { background: #d4edda; }
pre { background: #2d2d2d; color: #f8f8f2; padding: 12px; border-radius: 6px; overflow-x: auto; font-size: 13px; margin: 5px 0; }
#style-demo { border: 2px dashed #61dafb; padding: 15px; border-radius: 8px; margin: 10px 0; }
</style>
</head>
<body>
<h1>JSX与HTML的区别对照表</h1>
<div class="panel">
<h2>属性名差异</h2>
<table>
<tr>
<th>HTML属性</th>
<th>JSX属性</th>
<th>说明</th>
</tr>
<tr>
<td class="html-col"><code>class</code></td>
<td class="jsx-col"><code>className</code></td>
<td>class是JavaScript保留字</td>
</tr>
<tr>
<td class="html-col"><code>for</code></td>
<td class="jsx-col"><code>htmlFor</code></td>
<td>for是JavaScript保留字</td>
</tr>
<tr>
<td class="html-col"><code>tabindex</code></td>
<td class="jsx-col"><code>tabIndex</code></td>
<td>驼峰命名</td>
</tr>
<tr>
<td class="html-col"><code>onclick</code></td>
<td class="jsx-col"><code>onClick</code></td>
<td>驼峰命名</td>
</tr>
<tr>
<td class="html-col"><code>onchange</code></td>
<td class="jsx-col"><code>onChange</code></td>
<td>驼峰命名</td>
</tr>
<tr>
<td class="html-col"><code>readonly</code></td>
<td class="jsx-col"><code>readOnly</code></td>
<td>驼峰命名</td>
</tr>
<tr>
<td class="html-col"><code>maxlength</code></td>
<td class="jsx-col"><code>maxLength</code></td>
<td>驼峰命名</td>
</tr>
<tr>
<td class="html-col"><code>autofocus</code></td>
<td class="jsx-col"><code>autoFocus</code></td>
<td>驼峰命名</td>
</tr>
<tr>
<td class="html-col"><code>style="color: red;"</code></td>
<td class="jsx-col"><code>style={{ color: 'red' }}</code></td>
<td>JSX中style是对象</td>
</tr>
</table>
</div>
<div class="panel">
<h2>style属性差异详解</h2>
<p>HTML中style是字符串,JSX中style是JavaScript对象:</p>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div>
<h4>HTML写法</h4>
<pre><div style="
color: red;
font-size: 16px;
background-color: blue;
margin-top: 10px;
"></pre>
</div>
<div>
<h4>JSX写法</h4>
<pre><div style={{
color: 'red',
fontSize: '16px',
backgroundColor: 'blue',
marginTop: '10px'
}}></pre>
</div>
</div>
<h4>实际渲染效果:</h4>
<div id="style-demo"></div>
</div>
<div class="panel">
<h2>自闭合标签差异</h2>
<table>
<tr>
<th>HTML</th>
<th>JSX</th>
</tr>
<tr>
<td class="html-col"><code><br></code></td>
<td class="jsx-col"><code><br /></code></td>
</tr>
<tr>
<td class="html-col"><code><hr></code></td>
<td class="jsx-col"><code><hr /></code></td>
</tr>
<tr>
<td class="html-col"><code><img src="..."></code></td>
<td class="jsx-col"><code><img src="..." /></code></td>
</tr>
<tr>
<td class="html-col"><code><input type="text"></code></td>
<td class="jsx-col"><code><input type="text" /></code></td>
</tr>
</table>
</div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
function StyleDemo() {
const containerStyle = {
padding: '20px',
borderRadius: '8px',
backgroundColor: '#667eea',
color: 'white',
fontSize: '18px',
marginTop: '10px',
textAlign: 'center'
};
const boxStyle = {
display: 'inline-block',
padding: '10px 20px',
margin: '5px',
backgroundColor: 'rgba(255,255,255,0.2)',
borderRadius: '6px'
};
return (
<div style={containerStyle}>
<p>JSX style对象渲染效果</p>
<div style={boxStyle}>fontSize: 18px</div>
<div style={boxStyle}>borderRadius: 6px</div>
<div style={boxStyle}>backgroundColor: #667eea</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('style-demo'));
root.render(<StyleDemo />);
</script>
</body>
</html>JSX表达式
JSX中使用花括号{}嵌入JavaScript表达式:
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JSX表达式</title>
<style>
body { font-family: -apple-system, sans-serif; padding: 20px; max-width: 900px; margin: 0 auto; background: #f5f5f5; }
.panel { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin: 15px 0; }
.expr-card { border: 1px solid #e0e0e0; border-radius: 8px; padding: 15px; margin: 10px 0; }
.expr-card h4 { margin-top: 0; color: #61dafb; }
.expr-result { background: #f0faff; padding: 10px; border-radius: 4px; margin-top: 8px; }
#expr-demo { min-height: 100px; }
</style>
</head>
<body>
<h1>JSX表达式演示</h1>
<div id="expr-demo"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
function ExpressionDemo() {
// 各种JavaScript表达式
const name = 'React开发者';
const age = 25;
const skills = ['HTML', 'CSS', 'JavaScript'];
const isActive = true;
const user = { firstName: '张', lastName: '三' };
// 函数表达式
function formatName(user) {
return user.firstName + user.lastName;
}
// 对象表达式
const labelStyle = {
display: 'inline-block',
padding: '4px 12px',
borderRadius: '12px',
fontSize: '14px',
margin: '3px'
};
return (
<div>
<div className="panel">
<h3>1. 变量表达式</h3>
<div className="expr-card">
<h4>基本变量</h4>
<div className="expr-result">
<p>姓名:{name}</p>
<p>年龄:{age}</p>
<p>全名:{formatName(user)}</p>
</div>
</div>
</div>
<div className="panel">
<h3>2. 运算表达式</h3>
<div className="expr-card">
<h4>算术/逻辑/三元运算</h4>
<div className="expr-result">
<p>2 + 3 = {2 + 3}</p>
<p>年龄翻倍:{age * 2}</p>
<p>状态:{isActive ? '活跃' : '非活跃'}</p>
<p>技能数量:{skills.length} 个</p>
</div>
</div>
</div>
<div className="panel">
<h3>3. 字符串模板</h3>
<div className="expr-card">
<div className="expr-result">
<p>{`欢迎你,${formatName(user)}!今年${age}岁。`}</p>
</div>
</div>
</div>
<div className="panel">
<h3>4. 数组表达式</h3>
<div className="expr-card">
<h4>数组自动展开(不推荐,推荐用map)</h4>
<div className="expr-result">
<p>{['苹果', '香蕉', '橙子'].join(', ')}</p>
</div>
</div>
</div>
<div className="panel">
<h3>5. 对象表达式(style)</h3>
<div className="expr-card">
<div className="expr-result">
<span style={{...labelStyle, backgroundColor: '#61dafb', color: '#000'}}>React</span>
<span style={{...labelStyle, backgroundColor: '#42b883', color: '#fff'}}>Vue</span>
<span style={{...labelStyle, backgroundColor: '#dd0031', color: '#fff'}}>Angular</span>
</div>
</div>
</div>
<div className="panel">
<h3>6. 不能在JSX中使用的语句</h3>
<div className="expr-card">
<h4>以下不能在花括号中使用:</h4>
<div className="expr-result">
<p>- if/else 语句(用三元表达式替代)</p>
<p>- for/while 循环(用map替代)</p>
<p>- switch 语句(用三元或立即执行函数替代)</p>
<p>- 声明语句(var/let/const)</p>
</div>
</div>
</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('expr-demo'));
root.render(<ExpressionDemo />);
</script>
</body>
</html>条件渲染
React中的条件渲染有三种主要方式:
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JSX条件渲染</title>
<style>
body { font-family: -apple-system, sans-serif; padding: 20px; max-width: 900px; margin: 0 auto; background: #f5f5f5; }
.panel { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin: 15px 0; }
.method-tag { display: inline-block; padding: 3px 10px; border-radius: 12px; font-size: 13px; font-weight: bold; margin-right: 5px; }
.tag-if { background: #e74c3c; color: white; }
.tag-and { background: #3498db; color: white; }
.tag-ternary { background: #27ae60; color: white; }
.demo-area { border: 2px dashed #61dafb; padding: 15px; border-radius: 8px; margin: 10px 0; }
button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; margin: 5px; }
.btn-login { background: #27ae60; color: white; }
.btn-logout { background: #e74c3c; color: white; }
.btn-toggle { background: #3498db; color: white; }
.user-card { background: #f0faff; padding: 15px; border-radius: 8px; margin: 10px 0; }
.guest-card { background: #fff3cd; padding: 15px; border-radius: 8px; margin: 10px 0; }
.message { padding: 10px; border-radius: 4px; margin: 5px 0; }
.msg-info { background: #d1ecf1; color: #0c5460; }
.msg-warning { background: #fff3cd; color: #856404; }
.msg-error { background: #f8d7da; color: #721c24; }
</style>
</head>
<body>
<h1>JSX条件渲染</h1>
<div id="cond-demo"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// 方式1:if/else 语句(在JSX外部使用)
function UserGreeting({ name }) {
return (
<div className="user-card">
<h3>欢迎回来,{name}!</h3>
<p>你有 3 条未读消息</p>
</div>
);
}
function GuestGreeting() {
return (
<div className="guest-card">
<h3>请先登录</h3>
<p>登录后可以查看更多内容</p>
</div>
);
}
function Greeting({ isLoggedIn, userName }) {
// 方式1:if/else 在return之前
if (isLoggedIn) {
return <UserGreeting name={userName} />;
}
return <GuestGreeting />;
}
// 方式2:三元表达式
function MessageList({ messages }) {
return (
<div>
{messages.length > 0 ? (
<div>
<h4>消息列表({messages.length}条)</h4>
{messages.map((msg, i) => (
<div key={i} className={`message msg-${msg.type}`}>
{msg.text}
</div>
))}
</div>
) : (
<p style={{ color: '#999', textAlign: 'center', padding: '20px' }}>暂无消息</p>
)}
</div>
);
}
// 方式3:&& 短路运算
function NotificationPanel({ notifications, showPanel }) {
return (
<div>
{/* && 短路:条件为true时渲染 */}
{showPanel && (
<div style={{ background: '#e8f4fd', padding: '15px', borderRadius: '8px' }}>
<h4>通知面板</h4>
{notifications.map((n, i) => (
<p key={i}>{n}</p>
))}
</div>
)}
{!showPanel && <p style={{ color: '#999' }}>通知面板已隐藏</p>}
</div>
);
}
// 综合演示
function ConditionDemo() {
const [isLoggedIn, setIsLoggedIn] = React.useState(false);
const [userName, setUserName] = React.useState('张三');
const [showNotifications, setShowNotifications] = React.useState(true);
const messages = [
{ type: 'info', text: '系统将于今晚维护' },
{ type: 'warning', text: '您的会员即将到期' },
{ type: 'error', text: '支付失败,请重试' }
];
const notifications = ['新评论', '系统更新', '好友请求'];
return (
<div>
<div className="panel">
<h3><span className="method-tag tag-if">方式1</span> if/else 条件渲染</h3>
<button className={isLoggedIn ? 'btn-logout' : 'btn-login'}
onClick={() => setIsLoggedIn(!isLoggedIn)}>
{isLoggedIn ? '退出登录' : '登录'}
</button>
<div className="demo-area">
<Greeting isLoggedIn={isLoggedIn} userName={userName} />
</div>
</div>
<div className="panel">
<h3><span className="method-tag tag-ternary">方式2</span> 三元表达式</h3>
<div className="demo-area">
<MessageList messages={messages} />
</div>
</div>
<div className="panel">
<h3><span className="method-tag tag-and">方式3</span> && 短路运算</h3>
<button className="btn-toggle" onClick={() => setShowNotifications(!showNotifications)}>
{showNotifications ? '隐藏通知' : '显示通知'}
</button>
<div className="demo-area">
<NotificationPanel notifications={notifications} showPanel={showNotifications} />
</div>
</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('cond-demo'));
root.render(<ConditionDemo />);
</script>
</body>
</html>列表渲染
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JSX列表渲染</title>
<style>
body { font-family: -apple-system, sans-serif; padding: 20px; max-width: 900px; margin: 0 auto; background: #f5f5f5; }
.panel { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin: 15px 0; }
.product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 15px; }
.product-card { border: 1px solid #eee; border-radius: 8px; overflow: hidden; transition: transform 0.2s, box-shadow 0.2s; }
.product-card:hover { transform: translateY(-3px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
.product-card img { width: 100%; height: 140px; object-fit: cover; }
.product-info { padding: 12px; }
.product-name { font-weight: bold; margin: 0 0 5px; }
.product-price { color: #e74c3c; font-weight: bold; }
.product-tag { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 11px; margin-right: 4px; }
.tag-hot { background: #ffe0e0; color: #e74c3c; }
.tag-new { background: #e0ffe0; color: #27ae60; }
.tag-sale { background: #fff3cd; color: #856404; }
.nested-list { padding-left: 20px; }
.category-item { margin: 5px 0; }
.category-name { font-weight: bold; cursor: pointer; padding: 5px 10px; border-radius: 4px; }
.category-name:hover { background: #f0f0f0; }
.sub-item { padding: 3px 10px; margin: 2px 0; color: #666; }
button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; margin: 5px; background: #61dafb; color: #000; }
</style>
</head>
<body>
<h1>JSX列表渲染</h1>
<div id="list-demo"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// 基本列表渲染
function ProductList() {
const products = [
{ id: 1, name: 'React实战手册', price: 79.9, image: 'https://picsum.photos/seed/react/220/140', tags: ['hot'] },
{ id: 2, name: 'Vue3入门指南', price: 59.9, image: 'https://picsum.photos/seed/vue3/220/140', tags: ['new'] },
{ id: 3, name: 'TypeScript进阶', price: 89.9, image: 'https://picsum.photos/seed/ts/220/140', tags: ['sale'] },
{ id: 4, name: 'Node.js开发', price: 69.9, image: 'https://picsum.photos/seed/node/220/140', tags: [] },
{ id: 5, name: 'CSS权威指南', price: 99.9, image: 'https://picsum.photos/seed/css3/220/140', tags: ['hot', 'sale'] },
{ id: 6, name: 'Webpack实战', price: 49.9, image: 'https://picsum.photos/seed/webpack/220/140', tags: ['new'] }
];
const tagLabels = { hot: '热销', new: '新品', sale: '促销' };
const tagClasses = { hot: 'tag-hot', new: 'tag-new', sale: 'tag-sale' };
return (
<div className="panel">
<h3>基本列表渲染 - 商品卡片</h3>
<div className="product-grid">
{products.map(product => (
<div key={product.id} className="product-card">
<img src={product.image} alt={product.name} />
<div className="product-info">
<p className="product-name">{product.name}</p>
<p className="product-price">¥{product.price}</p>
<div>
{product.tags.map(tag => (
<span key={tag} className={`product-tag ${tagClasses[tag]}`}>
{tagLabels[tag]}
</span>
))}
</div>
</div>
</div>
))}
</div>
</div>
);
}
// 嵌套列表渲染
function NestedList() {
const [expandedId, setExpandedId] = React.useState(null);
const categories = [
{ id: 1, name: '前端开发', children: ['HTML/CSS', 'JavaScript', 'React', 'Vue', 'Angular'] },
{ id: 2, name: '后端开发', children: ['Node.js', 'Python', 'Java', 'Go'] },
{ id: 3, name: '移动开发', children: ['React Native', 'Flutter', 'Swift', 'Kotlin'] }
];
return (
<div className="panel">
<h3>嵌套列表渲染 - 分类目录</h3>
<div>
{categories.map(cat => (
<div key={cat.id} className="category-item">
<div
className="category-name"
onClick={() => setExpandedId(expandedId === cat.id ? null : cat.id)}
>
{expandedId === cat.id ? '▼' : '▶'} {cat.name}
</div>
{expandedId === cat.id && (
<div className="nested-list">
{cat.children.map((child, i) => (
<div key={i} className="sub-item">• {child}</div>
))}
</div>
)}
</div>
))}
</div>
</div>
);
}
// key重要性演示
function KeyDemo() {
const [items, setItems] = React.useState([
{ id: 'a', text: '项目A' },
{ id: 'b', text: '项目B' },
{ id: 'c', text: '项目C' }
]);
function addItem() {
const newId = 'item_' + Date.now();
setItems([{ id: newId, text: '新项目' }, ...items]);
}
function removeItem(id) {
setItems(items.filter(item => item.id !== id));
}
return (
<div className="panel">
<h3>key属性的重要性</h3>
<button onClick={addItem}>在头部添加项目</button>
<div style={{ marginTop: '10px' }}>
{items.map(item => (
<div key={item.id} style={{ display: 'flex', alignItems: 'center', padding: '8px', margin: '4px 0', background: '#f8f9fa', borderRadius: '4px' }}>
<span style={{ flex: 1 }}>ID: {item.id} - {item.text}</span>
<button onClick={() => removeItem(item.id)} style={{ background: '#e74c3c', color: 'white', padding: '4px 8px', fontSize: '12px' }}>删除</button>
</div>
))}
</div>
</div>
);
}
function App() {
return (
<div>
<ProductList />
<NestedList />
<KeyDemo />
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('list-demo'));
root.render(<App />);
</script>
</body>
</html>Fragment
Fragment用于包裹多个元素而不添加额外的DOM节点:
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>React Fragment</title>
<style>
body { font-family: -apple-system, sans-serif; padding: 20px; max-width: 900px; margin: 0 auto; background: #f5f5f5; }
.panel { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin: 15px 0; }
pre { background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 8px; overflow-x: auto; font-size: 13px; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background: #61dafb; }
.dom-inspect { background: #f0f0f0; padding: 10px; border-radius: 4px; font-family: monospace; font-size: 13px; margin: 10px 0; }
</style>
</head>
<body>
<h1>React Fragment</h1>
<div id="frag-demo"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
function FragmentDemo() {
const columns = [
{ key: 'name', label: '姓名' },
{ key: 'age', label: '年龄' },
{ key: 'city', label: '城市' }
];
const data = [
{ name: '张三', age: 25, city: '北京' },
{ name: '李四', age: 30, city: '上海' },
{ name: '王五', age: 28, city: '广州' }
];
return (
<div>
<div className="panel">
<h3>Fragment的使用场景</h3>
<p>当组件需要返回多个兄弟元素,但不希望添加额外的DOM包裹层时,使用Fragment:</p>
<pre>{`// 不使用Fragment:产生多余的div
return (
<div>
<td>姓名</td>
<td>年龄</td>
</div>
);
// 使用Fragment:不产生额外DOM
return (
<React.Fragment>
<td>姓名</td>
<td>年龄</td>
</React.Fragment>
);
// 简写语法
return (
<>
<td>姓名</td>
<td>年龄</td>
</>
);
// 带key的Fragment(简写不支持key)
{items.map(item => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}`}</pre>
</div>
<div className="panel">
<h3>实际应用:表格渲染</h3>
<p>表格行组件返回多个td,不能用div包裹:</p>
<table>
<thead>
<tr>
{columns.map(col => (
<th key={col.key}>{col.label}</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr key={i}>
{/* Fragment让每个td直接作为tr的子元素 */}
{columns.map(col => (
<React.Fragment key={col.key}>
<td>{row[col.key]}</td>
</React.Fragment>
))}
</tr>
))}
</tbody>
</table>
</div>
<div className="panel">
<h3>DOM结构对比</h3>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div>
<h4>使用div包裹</h4>
<div className="dom-inspect">
<table><br />
<tr><br />
<span style="color:#e74c3c"><div> ← 多余节点!</span><br />
<td>...</td><br />
</div><br />
</tr><br />
</table>
</div>
</div>
<div>
<h4>使用Fragment</h4>
<div className="dom-inspect">
<table><br />
<tr><br />
<span style="color:#27ae60"><td>...</td> ← 干净!</span><br />
</tr><br />
</table>
</div>
</div>
</div>
</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('frag-demo'));
root.render(<FragmentDemo />);
</script>
</body>
</html>JSX编译原理
JSX不是浏览器能直接理解的语法,它需要通过Babel等工具编译为JavaScript:
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JSX编译原理</title>
<style>
body { font-family: -apple-system, sans-serif; padding: 20px; max-width: 900px; margin: 0 auto; background: #f5f5f5; }
.panel { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin: 15px 0; }
pre { background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 8px; overflow-x: auto; font-size: 13px; }
.compile-flow { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 10px; margin: 20px 0; }
.flow-box { padding: 12px 20px; border-radius: 8px; font-weight: bold; text-align: center; }
.flow-jsx { background: #61dafb; color: #000; }
.flow-babel { background: #f5da55; color: #000; }
.flow-js { background: #f7df1e; color: #000; }
.flow-dom { background: #27ae60; color: white; }
.arrow { font-size: 24px; color: #666; }
textarea { width: 100%; min-height: 100px; padding: 10px; border: 2px solid #61dafb; border-radius: 8px; font-family: monospace; font-size: 14px; resize: vertical; }
.compile-output { background: #1e1e1e; color: #0f0; padding: 15px; border-radius: 8px; font-family: monospace; font-size: 13px; min-height: 100px; white-space: pre-wrap; }
button { padding: 10px 24px; border: none; border-radius: 6px; cursor: pointer; background: #61dafb; color: #000; font-weight: bold; font-size: 16px; }
button:hover { background: #4fa8d4; }
</style>
</head>
<body>
<h1>JSX编译原理</h1>
<div class="panel">
<h2>编译流程</h2>
<div class="compile-flow">
<div class="flow-box flow-jsx">JSX代码</div>
<div class="arrow">→</div>
<div class="flow-box flow-babel">Babel编译</div>
<div class="arrow">→</div>
<div class="flow-box flow-js">JavaScript</div>
<div class="arrow">→</div>
<div class="flow-box flow-dom">React DOM</div>
</div>
</div>
<div class="panel">
<h2>编译对照示例</h2>
<table style="width:100%;border-collapse:collapse;">
<tr style="background:#61dafb;">
<th style="padding:10px;text-align:left;">JSX</th>
<th style="padding:10px;text-align:left;">编译后JavaScript</th>
</tr>
<tr>
<td style="padding:10px;vertical-align:top;"><pre><div className="app">
<h1>Hello</h1>
<p>World</p>
</div></pre></td>
<td style="padding:10px;vertical-align:top;"><pre>React.createElement(
'div',
{ className: 'app' },
React.createElement('h1', null, 'Hello'),
React.createElement('p', null, 'World')
)</pre></td>
</tr>
<tr style="background:#f8f8f8;">
<td style="padding:10px;vertical-align:top;"><pre><Profile name="张三" age={25} /></pre></td>
<td style="padding:10px;vertical-align:top;"><pre>React.createElement(
Profile,
{ name: '张三', age: 25 }
)</pre></td>
</tr>
<tr>
<td style="padding:10px;vertical-align:top;"><pre><ul>
{items.map(i => (
<li key={i.id}>{i.name}</li>
))}
</ul></pre></td>
<td style="padding:10px;vertical-align:top;"><pre>React.createElement('ul', null,
items.map(function(i) {
return React.createElement(
'li',
{ key: i.id },
i.name
);
})
)</pre></td>
</tr>
</table>
</div>
<div class="panel">
<h2>在线编译器</h2>
<p>输入JSX代码,查看编译结果:</p>
<textarea id="jsxInput">const element = (
<div className="container">
<h1 id="title">Hello, {name}!</h1>
<p style={{ color: 'red' }}>Welcome</p>
</div>
);</textarea>
<div style="margin:10px 0;">
<button onclick="compileJSX()">编译</button>
</div>
<div id="compileResult" class="compile-output">点击"编译"按钮查看结果</div>
</div>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script>
function compileJSX() {
var input = document.getElementById('jsxInput').value;
try {
var output = Babel.transform(input, {
presets: ['react'],
plugins: []
});
document.getElementById('compileResult').textContent = output.code;
} catch (e) {
document.getElementById('compileResult').textContent = '编译错误: ' + e.message;
}
}
</script>
</body>
</html>三、浏览器兼容性
四、注意事项与最佳实践
提示:JSX防XSS注入:React默认对嵌入的表达式进行转义,防止XSS攻击。如需插入原始HTML,需使用dangerouslySetInnerHTML
-
布尔属性:
<input disabled />等同于<input disabled={true} />,省略属性值等同于true -
children prop:组件标签之间的内容作为children传递,可以是字符串、元素、函数等
-
key的选择:使用数据中的唯一标识作为key,避免使用数组索引(当列表会增删时)
-
条件渲染优先级:简单的显示/隐藏用&&,二选一用三元,复杂逻辑用if/else提取变量
-
JSX中的注释:使用
{/* 注释内容 */}格式
五、代码规范示例
代码示例
// 不推荐:在JSX中写复杂逻辑
function Component({ items }) {
return (
<div>
{items.filter(i => i.active).sort((a, b) => a.id - b.id).map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
// 推荐:将逻辑提取到变量或函数中
function Component({ items }) {
const activeItems = items
.filter(i => i.active)
.sort((a, b) => a.id - b.id);
return (
<div>
{activeItems.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
// 不推荐:嵌套三元表达式
{isLoading ? <Spinner /> : hasError ? <Error /> : data ? <Content data={data} /> : <Empty />}
// 推荐:使用辅助函数
function renderContent() {
if (isLoading) return <Spinner />;
if (hasError) return <Error />;
if (data) return <Content data={data} />;
return <Empty />;
}
// 不推荐:内联复杂样式对象
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '20px', backgroundColor: '#f0f0f0' }}>
// 推荐:提取样式常量
const containerStyle = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '20px',
backgroundColor: '#f0f0f0'
};
<div style={containerStyle}>六、常见问题与解决方案
常见问题
为什么JSX中不能使用if/for语句?
JSX花括号内只能包含表达式(expression),不能包含语句(statement)。if和for是语句,不是表达式。替代方案:用三元表达式替代if,用map替代for。
JSX中的花括号和模板字符串有什么区别?
JSX花括号{}用于嵌入JavaScript表达式,模板字符串${}用于字符串插值。在JSX中,两者可以结合使用:<p>{`Hello, ${name}!`}</p>。
为什么React要使用className而不是class?
因为class是JavaScript的保留字,在JSX(本质是JavaScript)中使用class会导致语法错误。React选择使用className来对应HTML的class属性,这与DOM API中的element.className一致。
dangerouslySetInnerHTML什么时候使用?
当需要渲染HTML字符串时使用,如富文本编辑器输出。但要注意安全风险,确保HTML内容可信或已消毒。使用方式:<div dangerouslySetInnerHTML={{ __html: htmlString }} />。
Fragment的短语法<></>和<React.Fragment>有什么区别?
功能上完全相同,但短语法不支持key属性。在列表渲染中需要key时,必须使用<React.Fragment key={...}>完整写法。
七、总结
本教程详细介绍了React JSX语法及其与HTML的差异。JSX是JavaScript的语法扩展,它让UI编写更直观,但本质上是React.createElement的语法糖。JSX与HTML的关键区别包括:属性名使用驼峰命名(className、htmlFor、onClick等)、style属性使用对象而非字符串、所有标签必须闭合。JSX通过花括号嵌入JavaScript表达式,支持条件渲染(if/else、三元、&&)和列表渲染(map)。Fragment解决了多根元素问题而不引入额外DOM节点。理解JSX的编译原理有助于写出更高效的React代码。下一教程将深入React组件与HTML模板的关系。
本文涉及AI创作
内容由AI创作,请仔细甄别