JSX 语法入门
在上一节中,我们创建了第一个 React 组件 PracticeCard
。我们看到组件返回了一些看起来像 HTML 的东西,那就是 JSX (JavaScript XML)。本节课我们将深入了解 JSX 的基本语法规则,并利用它来改进我们的卡片组件结构,使其更加清晰,并为下一节课学习如何通过 props
传递动态数据打下基础。
学习目标:
- 理解 JSX 的核心语法规则。
- 能够在 JSX 中嵌入 JavaScript 表达式。
- 掌握 JSX 属性的写法(特别是
className
和style
)。 - 使用 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-*
保持不变。
- HTML 的
-
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
文件,将其修改为:
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> );}
主要改动:
- 添加
className
: 我们给最外层的div
添加了className="practice-card"
。虽然现在还没有对应的 CSS 文件,但这是良好的实践,方便以后统一添加样式。我们还为内部结构添加了card-header
,card-body
,card-footer
的类名。 - 更清晰的结构: 使用了
h3
来表示标题,p
来表示描述,并添加了额外的div
来区分头部、主体和脚部。 - 嵌入变量: 我们在组件内部定义了
cardTitle
,cardDescription
,cardDate
变量,并使用{}
将它们嵌入到 JSX 中,使得内容部分是动态的(尽管现在还是硬编码在组件内部)。 - 改进
style
: 稍微调整了内联样式,使其看起来更像一个卡片。
现在,如果你运行应用 (npx run dev
) 并查看使用了 PracticeCard
的页面,你会看到卡片的结构变得更加清晰了。
小结与下一步
在本节中,我们深入学习了 JSX 的核心语法:
- 使用
{}
嵌入 JavaScript 表达式。 - 属性使用驼峰命名法 (
className
,htmlFor
),style
接受对象。 - 组件必须返回单一根元素(或使用
<>...</>
)。 - 空标签需要自闭合 (
<img />
)。 - 注释使用
{/* ... */}
。
我们还应用这些知识改进了 PracticeCard
的内部结构。
目前,卡片的数据(标题、描述)还是硬编码在组件内部。这限制了组件的复用性——我们无法轻易地用同一个 PracticeCard
组件来显示不同的练习内容。
下一步: 在下一节课中,我们将学习 React 的核心概念之一——Props (属性),它允许我们从父组件向子组件传递数据,让 PracticeCard
变得真正可复用和动态!