Skip to content

JSX 语法入门

在上一节中,我们创建了第一个 React 组件 PracticeCard。我们看到组件返回了一些看起来像 HTML 的东西,那就是 JSX (JavaScript XML)。本节课我们将深入了解 JSX 的基本语法规则,并利用它来改进我们的卡片组件结构,使其更加清晰,并为下一节课学习如何通过 props 传递动态数据打下基础。

学习目标:

  • 理解 JSX 的核心语法规则。
  • 能够在 JSX 中嵌入 JavaScript 表达式。
  • 掌握 JSX 属性的写法(特别是 classNamestyle)。
  • 使用 JSX 构建结构更清晰的卡片组件。

JSX 到底是什么?

简单来说,JSX 是 JavaScript 的一个语法扩展,它允许我们在 JavaScript 代码中编写类似 HTML 的标记。它使得描述用户界面 (UI) 结构更加直观和方便。

// 这是一个 JSX 表达式
const element = <h1>Hello, world!</h1>;

虽然它看起来像 HTML,但浏览器并不直接理解 JSX。在代码运行前,像 Next.js 或 Create React App 这样的工具会使用 Babel 这样的编译器将 JSX 转换为普通的 JavaScript 函数调用(例如 React.createElement(...))。

为什么要用 JSX?

  • 可读性高: JSX 的结构与最终渲染的 HTML 结构非常相似,使得代码更容易阅读和理解。
  • 表达力强: 可以轻松地在标记结构中嵌入 JavaScript 逻辑(变量、函数调用等)。
  • 开发效率: 比起手动编写 React.createElement 调用,JSX 更简洁、更直观,能提高开发效率。
  • 类型安全: 结合 TypeScript 或 Flow 使用时,JSX 可以提供编译时的类型检查,减少错误。

JSX 核心语法规则

要有效地使用 JSX,需要了解以下几个关键规则:

1. 嵌入 JavaScript 表达式 {}

你可以在 JSX 中使用花括号 {} 来嵌入任何有效的 JavaScript 表达式。这可以是变量、数学运算、函数调用、对象属性访问等。

function UserGreeting(props) {
const userName = "Alice";
const loggedIn = true;
return (
<div>
<h1>你好, {userName}!</h1> {/* 嵌入变量 */}
<p>当前时间: {new Date().toLocaleTimeString()}</p> {/* 嵌入函数调用 */}
<p>2 + 2 = {2 + 2}</p> {/* 嵌入数学运算 */}
{loggedIn && <p>您已登录。</p>} {/* 嵌入逻辑与操作符 (用于条件渲染) */}
</div>
);
}
  • 注意: {} 中只能放表达式,不能放 if/else 语句或 for 循环等语句。但可以使用三元运算符或逻辑与 (&&) 来实现条件渲染。

2. JSX 属性 (Attributes)

JSX 元素可以拥有属性,就像 HTML 标签一样。

  • 字符串字面量: 使用引号 "" 定义属性值。

    <img src="/images/avatar.png" alt="User Avatar" />
  • JavaScript 表达式: 使用花括号 {} 嵌入表达式作为属性值。这对于传递动态值、数字、布尔值、对象或数组非常有用。

    const imageUrl = "/images/logo.svg";
    const imageSize = 50;
    <img src={imageUrl} width={imageSize} height={imageSize} alt="Logo" />
  • 命名约定: JSX 属性通常使用驼峰命名法 (camelCase),而不是 HTML 的短横线分隔命名法。

    • HTML 的 class 属性在 JSX 中写为 className (因为 class 是 JavaScript 的保留关键字)。
    • HTML 的 for 属性(用于 <label>)在 JSX 中写为 htmlFor
    • 自定义属性 data-*aria-* 保持不变。
  • style 属性: style 属性接受一个 JavaScript 对象,而不是 CSS 字符串。属性名同样使用驼峰命名法。

    <div style={{ color: 'blue', fontSize: '16px', borderTop: '1px solid black' }}>
    Styled Text
    </div>

3. 必须有单一根元素

一个 React 组件返回的 JSX 必须被包裹在一个单一的顶层元素中。

// 错误:相邻的两个顶级元素
// return (
// <h1>Title</h1>
// <p>Paragraph</p>
// );
// 正确:使用一个 div 包裹
return (
<div>
<h1>Title</h1>
<p>Paragraph</p>
</div>
);
// 正确:使用 Fragment (不会在 DOM 中添加额外节点)
// 可以使用 <React.Fragment>...</React.Fragment> 或简写 <>...</>
return (
<>
<h1>Title</h1>
<p>Paragraph</p>
</>
);
  • Fragments (<>...</>) 是一个常见的解决方案,当你不想在 DOM 中添加额外的 div 时非常有用。

4. 自闭合标签

对于没有子元素的标签(如 <img>, <input>, <br>),必须使用 / 来闭合它们。

<img src="image.jpg" alt="description" />
<br />
<input type="text" />

5. JSX 中的注释

在 JSX 中添加注释需要使用 {/* ... */} 的语法。

<div>
{/* 这是一个 JSX 注释 */}
<h1>标题</h1>
{/*
多行注释
也可以这样写
*/}
<p>段落内容</p>
</div>

使用 JSX 改进卡片结构

现在,让我们运用刚学的 JSX 知识来改进上一节课创建的 PracticeCard 组件,给它一个更明确的结构。

打开 components/PracticeCard.js 文件,将其修改为:

components/PracticeCard.js
export default function PracticeCard() {
// 我们可以先在这里硬编码一些数据,下一节课学习用 props 传入
const cardTitle = "JSX 基础练习";
const cardDescription = "学习 JSX 的基本语法和如何在组件中使用它。";
const cardDate = new Date().toLocaleDateString(); // 获取当前日期
return (
<div className="practice-card" style={{ border: '1px solid #eee', padding: '16px', margin: '10px 0', borderRadius: '8px', backgroundColor: '#f9f9f9' }}>
{/* 卡片头部 */}
<div className="card-header" style={{ marginBottom: '12px', borderBottom: '1px solid #ddd', paddingBottom: '8px' }}>
<h3 style={{ margin: 0, color: '#333' }}>{cardTitle}</h3> {/* 使用 h3 显示标题 */}
</div>
{/* 卡片主体 */}
<div className="card-body" style={{ marginBottom: '12px' }}>
<p style={{ margin: '0 0 8px 0', color: '#555' }}>{cardDescription}</p> {/* 使用 p 显示描述 */}
</div>
{/* 卡片脚部 */}
<div className="card-footer" style={{ fontSize: '0.9em', color: '#777' }}>
<span>日期: {cardDate}</span> {/* 显示日期 */}
</div>
</div>
);
}

主要改动:

  1. 添加 className: 我们给最外层的 div 添加了 className="practice-card"。虽然现在还没有对应的 CSS 文件,但这是良好的实践,方便以后统一添加样式。我们还为内部结构添加了 card-header, card-body, card-footer 的类名。
  2. 更清晰的结构: 使用了 h3 来表示标题,p 来表示描述,并添加了额外的 div 来区分头部、主体和脚部。
  3. 嵌入变量: 我们在组件内部定义了 cardTitle, cardDescription, cardDate 变量,并使用 {} 将它们嵌入到 JSX 中,使得内容部分是动态的(尽管现在还是硬编码在组件内部)。
  4. 改进 style: 稍微调整了内联样式,使其看起来更像一个卡片。

现在,如果你运行应用 (npx run dev) 并查看使用了 PracticeCard 的页面,你会看到卡片的结构变得更加清晰了。

小结与下一步

在本节中,我们深入学习了 JSX 的核心语法:

  • 使用 {} 嵌入 JavaScript 表达式。
  • 属性使用驼峰命名法 (className, htmlFor),style 接受对象。
  • 组件必须返回单一根元素(或使用 <>...</>)。
  • 空标签需要自闭合 (<img />)。
  • 注释使用 {/* ... */}

我们还应用这些知识改进了 PracticeCard 的内部结构。

目前,卡片的数据(标题、描述)还是硬编码在组件内部。这限制了组件的复用性——我们无法轻易地用同一个 PracticeCard 组件来显示不同的练习内容。

下一步: 在下一节课中,我们将学习 React 的核心概念之一——Props (属性),它允许我们从父组件向子组件传递数据,让 PracticeCard 变得真正可复用和动态!