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 (
  &lt;h1&gt;标题&lt;/h1&gt;
  &lt;p&gt;段落&lt;/p&gt;
);

<span class="correct">// 正确:单个根元素</span>
return (
  &lt;div&gt;
    &lt;h1&gt;标题&lt;/h1&gt;
    &lt;p&gt;段落&lt;/p&gt;
  &lt;/div&gt;
);

<span class="correct">// 或使用Fragment</span>
return (
  &lt;&gt;
    &lt;h1&gt;标题&lt;/h1&gt;
    &lt;p&gt;段落&lt;/p&gt;
  &lt;/&gt;
);</pre>
    </div>

    <div class="rule">
      <h3>规则2:标签必须闭合</h3>
      <pre><span class="incorrect">// 错误:未闭合标签</span>
&lt;img src="photo.jpg"&gt;
&lt;br&gt;
&lt;input type="text"&gt;

<span class="correct">// 正确:自闭合标签</span>
&lt;img src="photo.jpg" /&gt;
&lt;br /&gt;
&lt;input type="text" /&gt;</pre>
    </div>

    <div class="rule">
      <h3>规则3:属性名使用驼峰命名</h3>
      <pre><span class="incorrect">// 错误:HTML属性名</span>
&lt;div class="container" tabindex="1"&gt;
&lt;label for="name"&gt;
&lt;div style="color: red;"&gt;

<span class="correct">// 正确:JSX属性名</span>
&lt;div className="container" tabIndex={1}&gt;
&lt;label htmlFor="name"&gt;
&lt;div style={{ color: 'red' }}&gt;</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,但有很多关键区别:

HTML属性 JSX属性 说明
class className class是JavaScript保留字
for htmlFor for是JavaScript保留字
tabindex tabIndex 驼峰命名
onclick onClick 驼峰命名
style="color: red;" style={{ color: 'red' }} JSX中style是对象

代码示例

<!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>&lt;div style="
  color: red;
  font-size: 16px;
  background-color: blue;
  margin-top: 10px;
"&gt;</pre>
      </div>
      <div>
        <h4>JSX写法</h4>
        <pre>&lt;div style={{
  color: 'red',
  fontSize: '16px',
  backgroundColor: 'blue',
  marginTop: '10px'
}}&gt;</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>&lt;br&gt;</code></td>
        <td class="jsx-col"><code>&lt;br /&gt;</code></td>
      </tr>
      <tr>
        <td class="html-col"><code>&lt;hr&gt;</code></td>
        <td class="jsx-col"><code>&lt;hr /&gt;</code></td>
      </tr>
      <tr>
        <td class="html-col"><code>&lt;img src="..."&gt;</code></td>
        <td class="jsx-col"><code>&lt;img src="..." /&gt;</code></td>
      </tr>
      <tr>
        <td class="html-col"><code>&lt;input type="text"&gt;</code></td>
        <td class="jsx-col"><code>&lt;input type="text" /&gt;</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">
                  &lt;table&gt;<br />
                  &nbsp;&nbsp;&lt;tr&gt;<br />
                  &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#e74c3c">&lt;div&gt; ← 多余节点!</span><br />
                  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&gt;...&lt;/td&gt;<br />
                  &nbsp;&nbsp;&nbsp;&nbsp;&lt;/div&gt;<br />
                  &nbsp;&nbsp;&lt;/tr&gt;<br />
                  &lt;/table&gt;
                </div>
              </div>
              <div>
                <h4>使用Fragment</h4>
                <div className="dom-inspect">
                  &lt;table&gt;<br />
                  &nbsp;&nbsp;&lt;tr&gt;<br />
                  &nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#27ae60">&lt;td&gt;...&lt;/td&gt; ← 干净!</span><br />
                  &nbsp;&nbsp;&lt;/tr&gt;<br />
                  &lt;/table&gt;
                </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>&lt;div className="app"&gt;
  &lt;h1&gt;Hello&lt;/h1&gt;
  &lt;p&gt;World&lt;/p&gt;
&lt;/div&gt;</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>&lt;Profile name="张三" age={25} /&gt;</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>&lt;ul&gt;
  {items.map(i =&gt; (
    &lt;li key={i.id}&gt;{i.name}&lt;/li&gt;
  ))}
&lt;/ul&gt;</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 = (
  &lt;div className="container"&gt;
    &lt;h1 id="title"&gt;Hello, {name}!&lt;/h1&gt;
    &lt;p style={{ color: 'red' }}&gt;Welcome&lt;/p&gt;
  &lt;/div&gt;
);</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>

三、浏览器兼容性

特性 Chrome Firefox Safari Edge 说明
JSX语法 N/A N/A N/A N/A 需要Babel编译,非浏览器特性
Babel浏览器版 60+ 60+ 12+ 79+ 仅用于开发/学习
className 全部 全部 全部 全部 React标准API
Fragment 全部 全部 全部 全部 React 16.2+
短语法Fragment 全部 全部 全部 全部 React 16.2+,Babel 7+

四、注意事项与最佳实践

提示: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模板的关系。

标签: JSX React HTML 条件渲染 列表渲染 Fragment Babel

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 框架集成:02 HTML与React集成 - 详细教程与实战指南 下一篇: 框架集成:04 React组件与HTML - 详细教程与实战指南

poll相关推荐