React JSX 看起来像 HTML——它会被编译成 JavaScript
JSX是JavaScript标记语言:具有单一根节点、封闭标签、驼峰式属性名,以及用于表达式的花括号——这些都是初学者最先接触到的基本规则。
在组件文件中,JSX 看起来像 HTML,但它会被编译成 JavaScript——这一事实几乎可以解释所有的语法规则。
前面的章节通常将组件返回的标记——例如 <h1>Hello</h1>——视为非正式的“标记”。它的真正名称是 JSX。这里的目标很简单:把 JSX 当作看起来像 HTML 的 JavaScript 来看待。一旦明确了这一点,大多数表面上的语法规则就不再显得随意了。
为何选择 JSX——将标记与逻辑结合
传统的前端开发方式是将结构放在 HTML 文件中,将行为放在 JavaScript 文件中。实际上,按钮的外观与点击处理函数很难分开处理。React 将两者都放在同一个单元——即组件——中,因此需要一种在 JavaScript 中编写标记的方式,而这种语法就是 JSX。
从外观上看,它与HTML很相似。由于编译器会将JSX转换为JavaScript,因此其规则比HTML更为严格。首先来了解初学者需要掌握的三条规则。
JSX的三条规则
规则1 —— 一个根节点
一个组件只能返回一个JSX树。多个同级元素必须位于同一个父节点之下。
// 🔴 two siblings side by side — error
return (
<h1>Title</h1>
<p>Content</p>
);
// ✅ wrapped in one
return (
<div>
<h1>Title</h1>
<p>Content</p>
</div>
);
如果额外的<div>会导致DOM结构混乱,可将这些同级元素包裹在Fragment中(<>...</>)。
return (
<>
<h1>Title</h1>
<p>Content</p>
</>
);
为什么要求只有一个根节点?因为JSX最终会变成JavaScript值。函数无法直接返回两个值,必须将它们封装起来;JSX也是如此。“这是JavaScript,不是HTML”这一概念使得这条规则合理存在。
规则2 —— 每个标签都必须闭合
HTML允许某些标签如<img>或<br>不使用闭合标签。而在JSX中每个标签都必须闭合。没有子内容的标签则使用自闭合形式/>。
// 🔴 an HTML habit — error
<img src="logo.png">
<input type="text">
// ✅ self-closing
<img src="logo.png" />
<input type="text" />
规则3 — camelCase命名法
JSX中的属性会成为JavaScript对象的关键字,因此其命名需遵循JavaScript的规范(camelCase),而非原始HTML的命名方式。常见的保留字冲突包括:
class→className(class在JavaScript中是保留字)for→htmlFor(for也是保留字)
// 🔴 straight from HTML
<div class="card">
<label for="email">Email</label>
</div>
// ✅ the JSX way
<div className="card">
<label htmlFor="email">Email</label>
</div>
同样的映射规则会将onclick转换为onClick,将tabindex转换为tabIndex。无需记住完整列表:编辑器自动补全功能以及TypeScript能快速识别出错误的名称。
花括号{}——JSX中的JavaScript
正是在这里,JSX才不再仅仅是静态模板。由于JSX本身就是JavaScript,花括号{}可用于直接嵌入JavaScript值。HTML只能存储固定文本,而JSX则可以计算表达式。
function Greeting() {
const name = 'Jane';
return <h1>Hello, {name}!</h1>; // inside the braces is JavaScript
}
{name}会被替换为name的值(即'Jane')。花括号可以接受任何表达式——也就是能够计算出数值的代码,而不仅仅是单纯的变量。
function Bill() {
const price = 12000;
const count = 3;
return (
<div>
<p>Unit price: {price} won</p>
<p>Quantity: {count}</p>
<p>Total: {price * count} won</p> {/* arithmetic works too */}
<p>{new Date().getFullYear()} receipt</p> {/* function calls work too */}
</div>
);
}
花括号也可用于属性值中。在这种用法下,无需在花括号周围加引号。
function Avatar() {
const user = { name: 'Jane Kim', imageUrl: '/me.png' };
return <img src={user.imageUrl} alt={user.name} />;
}
src="..."— 引号表示字面字符串。src={...}— 大括号表示JavaScript值。
仅允许使用表达式
在大括号内可以放置能生成值的表达式,但不能放置if、for或const之类的语句。
// 🔴 an if statement isn't an expression, so this fails
<p>{if (count > 0) 'in stock'}</p>
// ✅ the ternary operator is an expression, so it works
<p>{count > 0 ? 'in stock' : 'out of stock'}</p>
使用三元运算符、&&及相关模式实现的条件UI应放在专门的条件渲染章节中。目前的原则是:只有能生成值的代码才能放入大括号内。
双大括号{{ }} — 大括号内的对象
在 JSX 中经常出现的 {{ }} 模式并非独立的语言特性,而是嵌在外层括号内的JavaScript 对象 { },内联样式就是典型的例子。
// outer { } = "I'm putting JavaScript here"
// inner { } = the object itself
<div style={{ color: 'tomato', fontSize: 20 }}>styled</div>
HTML 会使用类似 style="color: tomato" 的字符串形式,而 JSX 则传递的是对象,因此属性名采用驼峰式命名(如 fontSize 而非 font-size),其值通常为字符串或数字。如果双大括号看起来难以理解,可先将其拆分为带名称的变量。
const cardStyle = { color: 'tomato', fontSize: 20 };
<div style={cardStyle}>styled</div> // exactly the same as above
外层括号表示“在此处插入 JavaScript 值”,而内部对象即为该值。
如何编写注释
JSX 中的注释也使用括号:即用 {} 包裹的 JavaScript 块级注释。
return (
<div>
{/* this is a comment inside JSX */}
<h1>Title</h1>
</div>
);
综合应用
上述规则都可以放在一个产品卡片组件中。
// src/ProductCard.tsx
function ProductCard() {
const product = {
name: 'Mechanical Keyboard',
price: 89000,
inStock: true,
imageUrl: "https://picsum.photos/200/300",
};
return (
<div className="card" style={{ padding: 16 }}>
{/* image: self-closing + brace attributes */}
<img src={product.imageUrl} alt={product.name} />
<h2>{product.name}</h2>
<p>Price: {product.price.toLocaleString()} won</p>
{/* show stock with an expression (ternary) — more in the next chapter */}
<p>{product.inStock ? 'In stock' : 'Out of stock'}</p>
</div>
);
}
export default ProductCard;
那个示例运用了全部三种结构规则(单一根节点、闭合标签、className),以及所有常见的大括号用法(文本插值、属性、算术运算、三元运算、样式对象)。
总结
- JSX是JavaScript,而非HTML——正因如此其语法比HTML更为严格。
- 三种结构规则:将兄弟元素包裹在同一个根节点中(使用Fragment即可),每个标签都必须闭合,属性名需采用驼峰式命名(
class→className,for→htmlFor)。 - 大括号
{}用于包裹JavaScript表达式,无论是在文本中还是属性中。 - 只有能生成值的表达式才能放在大括号内——诸如
if或for这样的语句则不行。
{{ }}并非新语法,而是大括号内的对象。内联样式就是典型的例子。接下来的自然步骤是从外部向组件传递数据——即props。在组件内部硬编码product对象仅适用于演示;而通过props,同一个ProductCard就能渲染多个产品,这时TypeScript的类型系统才真正发挥重要作用。
参考资料
- React关于JSX标记的文档
- React关于嵌入表达式的文档