JSX 是 JavaScript 的语法扩展,让你可以在代码里编写类似 HTML 的标记。React 组件返回 JSX 来描述屏幕上应该显示什么,构建工具再把它编译成普通的 JavaScript 函数调用。它看起来像 HTML,但有几条规则不同,而且可以用花括号把任意 JavaScript 值放进标记中。
{} 里的一切都是 JavaScript:属性、方法调用,甚至是把数组变成列表项的 map。往 skills 数组里加一个 'design',数量和列表都会随之更新。
JSX 规则
返回一个根元素
组件只返回一个值,所以它的 JSX 需要一个父元素。顶层有两个相邻元素就是语法错误。如果不想在页面里多出一个 <div>,就用 Fragment <>...</> 把它们包起来:
// Error: Adjacent JSX elements must be wrapped in an enclosing tag
return (
<h1>Title</h1>
<p>Text</p>
);
// OK: a fragment groups them without adding a DOM element
return (
<>
<h1>Title</h1>
<p>Text</p>
</>
);
如果需要给这一组加 key,请使用完整写法 <Fragment key={id}>,见 Fragment。
闭合每一个标签
HTML 允许某些标签不闭合,JSX 不允许。没有子元素的元素用 /> 自闭合:<img />、<br />、<input />,以及你自己的组件,比如 <Avatar />。
使用 className、htmlFor 和驼峰命名的属性
JSX 属性会变成 JavaScript 对象的键,所以遵循 JavaScript 的命名方式。class 和 for 是 JavaScript 的保留字,因此 JSX 使用 className 和 htmlFor。由多个单词组成的属性和所有事件处理函数都用驼峰命名:tabIndex、maxLength、autoFocus、onClick、onChange。有两个例外保留了连字符:aria-* 和 data-* 属性的写法和 HTML 完全一样。
把 className="field" 改成 class="field"。label 的样式不变,因为 React 还是会设置这个属性,但开发构建会打印“Invalid DOM property class. Did you mean className?”。预览以生产构建的方式运行,所以你在这里看不到这条警告;请使用 className。字符串属性用引号(type="email"),其他值用花括号(maxLength={40}、disabled={false})。
花括号里放表达式,不能放语句
在 {} 里可以写任何能产生值的东西:变量、算术运算、函数调用、三元运算符、&&、模板字符串。if、for、const 这样的语句不产生值,所以不能放在那里。要么在 return 之前把事情做完,要么改用表达式的写法:
点击之前仔细看:页面上多了一个 0。当数量为零时,count && <p>...</p> 的结果是 0,而 React 会渲染数字,零也不例外。把它改成 count > 0 && <p>You have mail.</p>,0 就消失了。false、null、undefined 和 true 不会渲染任何内容,所以 && 左边是布尔值时是安全的。条件渲染一页介绍了这些写法。
内联样式使用对象
style 属性接收的是 JavaScript 对象,而不是 CSS 字符串。这就形成了“双花括号”的样子:外层表示“这里是 JavaScript”,内层是对象字面量。属性名用驼峰命名(backgroundColor、fontSize),对大多数尺寸属性来说,纯数字表示像素。
因为样式是一个对象,所以它可以像其他值一样根据 state 计算出来。试着把 fontSize: big ? 24 : 16 改成 fontSize: big ? '2em' : '1em':字符串会连同单位原样传过去。
注释
JSX 中的注释要放在花括号里:{/* this is a comment */}。HTML 注释(<!-- -->)是语法错误。在标记之外的函数体中,普通的 // 注释照常可用。
return (
<div>
{/* The header is hidden on small screens */}
<Header />
</div>
);
把 HTML 转换成 JSX
把 HTML 粘贴进组件是常见的第一步,大部分内容都能直接用。通常需要做这些修改:
class改成className,for改成htmlFor。- 空标签要自闭合:
<br>改成<br />,<img src="a.png">改成<img src="a.png" />。 style="color: red; font-size: 14px"改成style={{ color: 'red', fontSize: 14 }}。- 事件属性接收函数,而不是字符串:
onclick="save()"改成onClick={save}。 - 属性名改成驼峰命名:
tabindex改成tabIndex,SVG 的stroke-width改成strokeWidth。 <!-- comments -->改成{/* comments */}。- 多个顶层元素需要一个父元素或一个 Fragment。
在线的 HTML 转 JSX 工具可以替你完成这些步骤,但了解它们之后,漏改某一处时你就能轻松看懂错误信息。
JSX 会编译成什么
浏览器无法运行 JSX。编译器(比如 Babel、SWC 或 Oxc,Vite 和各个框架会替你运行)会把每个标签变成来自 react/jsx-runtime 的函数调用:
// What you write
const element = <h1 className="title">Hello, {name}</h1>;
const button = <Button size="small">OK</Button>;
// What the compiler produces (roughly)
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';
const element = _jsxs('h1', { className: 'title', children: ['Hello, ', name] });
const button = _jsx(Button, { size: 'small', children: 'OK' });
小写的标签会变成字符串('h1',即 HTML 元素),首字母大写的标签会变成对你的函数的引用(Button),属性变成 props 对象,内容变成 children。旧的配置会编译成 React.createElement(type, props, ...children),所以旧文件即使从不提到 React,开头也会写 import React from 'react'。这个调用返回一个普通对象,叫做 React 元素。你可以把它打印出来:
控制台显示 heading 只是数据:一个类型 'h3' 和一个 props 对象。这也是 JSX 可以存进变量、传给函数、从函数返回的原因。
JSX 会替你转义文本
放进花括号的任何字符串都会作为文本渲染,绝不会被当作 HTML。如果用户在评论框里输入 <img src=x onerror=alert(1)>,React 会在屏幕上显示这些字符,而不会创建一个图片标签。这默认就挡住了最常见的跨站脚本攻击(XSS)。
第一个段落把标签作为普通字符打印出来。第二个段落使用了 dangerouslySetInnerHTML,它会跳过转义,把字符串作为真正的 HTML 插入,所以“our own”是粗体。这个长长的名字本身就是警告:只传入你自己生成的 HTML,或者经过 DOMPurify 等清理工具处理过的 HTML。把用户输入传进去,就会重新打开 JSX 已经堵上的 XSS 漏洞。
常见问题
JSX 是什么的缩写?
JSX 是 JavaScript XML 的缩写。它是一种语法扩展,让你可以在 JavaScript 代码中编写看起来像 HTML 的标记。
JSX 是 HTML 吗?
不是。JSX 看起来像 HTML,但会被编译成创建 React 元素的 JavaScript 函数调用。所以它用 className 代替 class,属性名采用驼峰命名,而且每个标签都必须闭合。
可以不用 JSX 写 React 吗?
可以。JSX 是可选的:你可以直接调用 react 中的 createElement('h1', { className: 'title' }, 'Hello')。几乎所有 React 代码库都使用 JSX,因为嵌套的标记读起来要容易得多。
为什么不能在 JSX 里使用 if 语句?
JSX 中的花括号只接受表达式,也就是能产生值的东西,而 if 是语句。请使用三元运算符(cond ? a : b)、&&,或者在 return 之前先把值算好存进变量。
JSX 里怎么写注释?
用花括号包住 JavaScript 注释:{/* like this */}。HTML 注释(<!-- -->)在 JSX 中是语法错误。