JSX(JavaScript XML)是 React 生态中最具辨识度的特性之一,它将类 HTML 的语法嵌入 JavaScript 中,让开发者能够以直观的方式编写 UI 结构,同时保留 JavaScript 的逻辑能力。很多开发者最初会将 JSX 误认为是 “HTML 在 JS 中的变体”,但实际上它是 JavaScript 的语法糖,最终会被编译为普通的 JavaScript 函数调用。本文将从本质、基础语法、进阶用法、常见误区四个维度,全面解析 JSX 的使用方法,帮助你彻底掌握这一核心技能。
一、JSX 是什么?—— 不止是 “HTML+JS”
1. JSX 的本质:语法糖
JSX 是 Facebook 为 React 开发的一种语法扩展,其核心作用是简化 React 元素的创建。当我们编写 JSX 代码时,Babel(或 TypeScript)会将其编译为 React 的createElement函数调用(React 17 + 也支持更简洁的jsx/jsxs函数)。
举个例子:
// 我们编写的JSX代码 const element = <h1 className="title">Hello, JSX!</h1>;编译后的 JavaScript 代码(React 17 之前):
const element = React.createElement( 'h1', // 元素类型 { className: 'title' }, // 元素属性 'Hello, JSX!' // 子元素 );React 17 + 的编译结果(无需显式引入 React):
import { jsx as _jsx } from 'react/jsx-runtime'; const element = _jsx('h1', { className: 'title', children: 'Hello, JSX!' });从编译结果可以看出:JSX 最终会被转换为描述 UI 的 JavaScript 对象(React 元素),而不是直接渲染为 DOM 节点。这也是 JSX 能够与 JavaScript 逻辑无缝结合的根本原因。
2. 为什么要用 JSX?
在 JSX 出现之前,开发者需要通过React.createElement手动创建 UI 元素,代码冗长且可读性差。JSX 的出现解决了以下问题:
- 直观性:类 HTML 的语法让 UI 结构一目了然,比纯 JavaScript 代码更易读、易维护;
- 无缝集成逻辑:可以在 JSX 中直接嵌入 JavaScript 表达式,实现 UI 与业务逻辑的紧密结合;
- 编译时检查:Babel 和 TypeScript 会在编译阶段检查 JSX 的语法错误,提前规避运行时问题;
- 组件化支持:JSX 天然支持 React 组件的嵌套和组合,是 React 组件化思想的核心载体。
注意:JSX 并非 React 的强制要求,你可以始终使用
React.createElement编写代码,但几乎所有 React 项目都会选择 JSX 以提升开发效率。
二、JSX 的核心语法规则:必掌握的基础
JSX 虽然看起来像 HTML,但本质是 JavaScript,因此有一套自己的语法规则。以下是最核心的规则,也是新手最容易踩坑的地方。
1. 标签必须闭合
与 HTML 不同,JSX 要求所有标签必须显式闭合,包括单标签(如<input>、<img>)。
// 错误:标签未闭合 const input = <input type="text">; const img = <img src="logo.png">; // 正确:单标签使用自闭合语法 const input = <input type="text" />; const img = <img src="logo.png" alt="logo" />; // 双标签必须成对出现 const div = <div>Hello, JSX</div>;2. 只能有一个根元素
JSX 表达式中不能直接返回多个同级元素,必须用一个根元素包裹(或使用 Fragment 片段)。
// 错误:多个根元素 const App = () => { return ( <h1>标题</h1> <p>内容</p> ); }; // 正确:用div作为根元素 const App = () => { return ( <div> <h1>标题</h1> <p>内容</p> </div> ); };3. 类名使用className而非class
在 JavaScript 中,class是关键字,因此 JSX 中不能使用class属性定义 CSS 类名,而是使用className(对应 DOM 的className属性)。
// 错误:使用class关键字 const element = <div class="container">Hello</div>; // 正确:使用className const element = <div className="container">Hello</div>;补充:在 React Native 中,类名使用
style属性,而不是className。
4. 表单标签的for属性改为htmlFor
同理,for是 JavaScript 的关键字,JSX 中使用htmlFor替代<label>标签的for属性。
// 错误:使用for关键字 const label = <label for="username">用户名:</label>; // 正确:使用htmlFor const label = <label htmlFor="username">用户名:</label>; <input id="username" type="text" />;5. 内联样式是对象形式
JSX 中的内联样式不能直接写 CSS 字符串,而是需要传递一个样式对象,属性名采用驼峰命名法(如fontSize而非font-size)。
// 错误:CSS字符串形式 const element = <div style="font-size: 16px; color: red;">Hello</div>; // 正确:样式对象形式 const element = <div style={{ fontSize: '16px', color: 'red' }}>Hello</div>; // 推荐:将样式抽离为变量 const textStyle = { fontSize: '16px', color: 'red', marginTop: '10px' // 驼峰命名法 }; const element = <div style={textStyle}>Hello</div>;6. 插入 JavaScript 表达式:使用{}
这是 JSX 最强大的特性之一:可以通过大括号{}在 JSX 中嵌入任意有效的 JavaScript 表达式(注意:是表达式,不是语句)。
// 1. 变量 const name = 'React'; const element = <h1>Hello, {name}!</h1>; // 2. 算术运算 const a = 10; const b = 20; const element = <p>10 + 20 = {a + b}</p>; // 3. 函数调用 const getGreeting = (name) => `Hello, ${name}!`; const element = <h1>{getGreeting('JSX')}</h1>; // 4. 三元运算符(条件表达式) const isLogin = true; const element = <p>{isLogin ? '已登录' : '请登录'}</p>; // 5. 数组(会自动展开) const list = ['苹果', '香蕉', '橙子']; const element = <div>{list}</div>; // 渲染为:<div>苹果香蕉橙子</div>注意:
{}中只能放表达式(有返回值的代码),不能放语句(如 if、for、switch 等)。如果需要使用语句,需在 JSX 外部处理。
7. JSX 中的注释
JSX 中的注释需要写在{}内,格式为/* 注释内容 */(单行注释也可以用//,但需要注意换行)。
const element = ( <div> {/* 这是JSX中的多行注释 */} <h1>Hello, JSX!</h1> {/* 单行注释也可以这样写 */} {/* 多行注释 可以换行 */} <p>{/* 行内注释 */}这是内容</p> </div> ); // 单行注释的另一种写法(注意换行) const element = ( <div> {/* 推荐 */} <h1>Hello, JSX!</h1> // 这种写法会报错,因为//不在{}内 <p>{// 这种写法可行,但需要换行 '内容'}</p> </div> );三、JSX 的进阶用法:从基础到实战
掌握了基础语法后,我们来看看 JSX 在实际开发中的高频进阶用法。
1. 片段(Fragment):避免多余的根节点
前面提到 JSX 必须有一个根元素,但有时我们不想添加额外的<div>等节点(避免 DOM 层级过深),此时可以使用React Fragment(片段),它会在渲染时被忽略,只保留子元素。
用法 1:<React.Fragment>
import React from 'react'; const App = () => { return ( <React.Fragment> <h1>标题</h1> <p>内容</p> <button>按钮</button> </React.Fragment> ); };用法 2:空标签<> </>(简写形式)
这是 React 16.2 + 支持的简写语法,功能与<React.Fragment>一致,但不支持添加属性(如 key)。
const App = () => { return ( <> <h1>标题</h1> <p>内容</p> <button>按钮</button> </> ); };用法 3:带 key 的 Fragment(仅支持完整写法)
当在列表中渲染 Fragment 时,需要为其添加 key 属性,此时必须使用完整的<React.Fragment>。
const list = [ { id: 1, text: '第一项' }, { id: 2, text: '第二项' } ]; const App = () => { return ( <div> {list.map(item => ( <React.Fragment key={item.id}> <p>{item.text}</p> <hr /> </React.Fragment> ))} </div> ); };2. 列表渲染:使用map并添加key
在 JSX 中渲染列表(如数组)时,通常使用Array.prototype.map方法,且必须为每个列表项添加唯一的key属性。
const todos = [ { id: 1, text: '学习JSX' }, { id: 2, text: '学习React' }, { id: 3, text: '开发项目' } ]; const TodoList = () => { return ( <ul> {todos.map(todo => ( // 正确:使用唯一的id作为key <li key={todo.id}>{todo.text}</li> ))} </ul> ); };关于key的重要注意事项:
key的作用:帮助 React 识别列表中元素的变化(添加、删除、排序),从而优化渲染性能;key必须是唯一的:在同一列表中,每个元素的 key 不能重复;- 不要使用索引作为 key:如果列表的顺序发生变化(如排序、删除),索引会重新分配,导致 React 误判元素变化,引发性能问题或渲染错误;
key只在列表内部有效:key 是给 React 看的,不会传递给组件,因此不能在组件内部通过props.key获取。
3. 条件渲染:多种实现方式
在 JSX 中实现条件渲染有多种方式,可根据场景选择:
方式 1:三元运算符(适合简单条件)
const isLogin = true; const UserInfo = () => { return ( <div> {isLogin ? ( <p>欢迎回来,用户!</p> ) : ( <button>请登录</button> )} </div> ); };方式 2:逻辑与运算符&&(适合 “存在即渲染” 的场景)
const hasUnreadMsg = true; const unreadCount = 5; const MsgTip = () => { return ( <div> {/* 当hasUnreadMsg为true时,渲染后面的元素;为false时,返回false,不渲染 */} {hasUnreadMsg && <span className="badge">{unreadCount}</span>} </div> ); };方式 3:外部条件语句(适合复杂条件)
const UserRole = ({ role }) => { // 外部定义渲染逻辑 let content; if (role === 'admin') { content = <p>管理员</p>; } else if (role === 'user') { content = <p>普通用户</p>; } else { content = <p>游客</p>; } return <div>{content}</div>; };方式 4:组件提取(适合极复杂的条件)
将不同条件的渲染逻辑提取为独立组件,让代码更清晰。
const AdminPanel = () => <p>管理员面板</p>; const UserPanel = () => <p>用户面板</p>; const GuestPanel = () => <p>游客面板</p>; const Panel = ({ role }) => { switch (role) { case 'admin': return <AdminPanel />; case 'user': return <UserPanel />; default: return <GuestPanel />; } };4. 自定义组件的渲染:首字母大写
在 JSX 中渲染自定义 React 组件时,组件名必须以大写字母开头(这是 React 的约定,用于区分原生 HTML 标签)。
// 正确:组件名首字母大写 const Button = () => <button>自定义按钮</button>; const App = () => { return ( <div> <Button /> {/* 渲染自定义组件 */} <button>原生按钮</button> {/* 渲染原生HTML标签 */} </div> ); }; // 错误:组件名小写,React会将其视为原生HTML标签(不存在的标签会渲染为<div>或报错) const button = () => <button>自定义按钮</button>; const App = () => { return <button />; // 渲染原生<button>,而非自定义组件 };5. 属性传递(Props):向组件传递数据
可以通过 JSX 的属性(props)向自定义组件传递数据,属性名同样采用驼峰命名法(如onClick、dataId)。
// 子组件接收props const Greeting = (props) => { return <h1>Hello, {props.name}!</h1>; }; // 父组件传递props const App = () => { return ( <div> {/* 传递字符串属性 */} <Greeting name="React" /> {/* 传递非字符串属性(需用{}包裹) */} <Greeting name={123} /> {/* 传递布尔值 */} <Greeting isShow={true} /> {/* 传递函数 */} <Greeting onButtonClick={() => alert('点击了')} /> {/* 传递JSX元素(子元素,对应props.children) */} <Greeting> <p>这是子元素</p> </Greeting> </div> ); };补充:
props.children是一个特殊的 props,用于接收组件的子元素(如上面的<p>这是子元素</p>)。
6. 危险的 HTML 渲染:dangerouslySetInnerHTML
默认情况下,React 会转义 JSX 中的所有内容,防止 XSS 攻击(跨站脚本攻击)。但有时我们需要渲染原始的 HTML 字符串(如后端返回的富文本),此时可以使用dangerouslySetInnerHTML属性(注意:使用该属性存在安全风险,需确保内容是可信的)。
// 原始HTML字符串 const htmlContent = '<p style="color: red;">这是富文本内容</p>'; // 错误:React会转义HTML标签,渲染为纯文本 const element = <div>{htmlContent}</div>; // 正确:使用dangerouslySetInnerHTML渲染原始HTML const element = <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;警告:不要将用户输入的内容直接通过 dangerouslySetInnerHTML 渲染,否则可能导致 XSS 攻击。如果必须渲染用户输入,需先进行 HTML 转义或过滤。
7. JSX 作为变量、返回值和参数
由于 JSX 最终会被编译为 JavaScript 对象,因此它可以作为变量存储、作为函数返回值、作为参数传递给函数。
// 1. 作为变量 const header = <h1>Hello, JSX</h1>; // 2. 作为函数返回值 const getHeader = () => { return <h1>Hello, JSX</h1>; }; // 3. 作为参数传递 const renderElement = (element) => { return <div>{element}</div>; }; const App = () => { return renderElement(header); };四、JSX 的常见误区与避坑指南
即使是有经验的开发者,也可能在使用 JSX 时踩坑。以下是最常见的误区及解决方案:
误区 1:混淆 HTML 和 JSX 的语法差异
问题:使用class、for、style等 HTML 属性,导致语法错误或样式不生效。解决方案:牢记 JSX 的属性替换规则:
class→classNamefor→htmlForstyle→ 驼峰命名的样式对象- 自定义属性:使用
data-*前缀(如data-id),React 会保留这些属性。
误区 2:在{}中使用语句(而非表达式)
问题:在 JSX 的{}中写入 if、for、switch 等语句,导致编译错误。
jsx
// 错误:if是语句,不能放在{}内 const element = <div>{if (true) { return 'Hello' }}</div>;解决方案:将语句移到 JSX 外部,或使用三元运算符、逻辑与等表达式替代。
误区 3:列表渲染忘记加key或使用索引作为key
问题:列表渲染时未添加key,控制台出现警告;或使用索引作为key,导致列表排序 / 删除时渲染异常。解决方案:使用唯一的 ID(如后端返回的 id、UUID)作为key;如果确实没有唯一 ID,可考虑生成唯一标识(如item.name + item.index),但尽量避免使用索引。
误区 4:过度使用dangerouslySetInnerHTML
问题:随意使用dangerouslySetInnerHTML渲染不可信内容,导致 XSS 攻击风险。解决方案:
- 尽量避免使用
dangerouslySetInnerHTML; - 如果必须使用,确保内容是可信的(如后端自己生成的富文本);
- 对用户输入的内容进行 HTML 转义(如使用
he库)。
误区 5:忽略 JSX 的大小写敏感
问题:将原生 HTML 标签大写(如<Div>),或自定义组件小写(如<button>),导致渲染错误。解决方案:
- 原生 HTML 标签:全小写(如
<div>、<button>); - 自定义组件:首字母大写(如
<Button>、<TodoList>)。
误区 6:直接修改props或state后渲染 JSX
问题:修改props或state的原始值(如数组的push、对象的属性赋值),导致 React 无法检测到变化,JSX 不更新。解决方案:遵循 React 的不可变原则,创建新的数组 / 对象(如使用concat、map、spread运算符)。
五、JSX 的优势:为什么它能成为 React 的标配?
总结一下,JSX 之所以能成为 React 开发的核心工具,主要有以下优势:
- 直观性:类 HTML 的语法让 UI 结构与代码逻辑分离但又紧密结合,比纯 JavaScript 更易读;
- 灵活性:可以嵌入任意 JavaScript 表达式,实现复杂的逻辑渲染;
- 安全性:默认转义内容,防止 XSS 攻击;
- 组件化:天然支持 React 的组件化思想,便于复用和维护;
- 跨平台:不仅可以用于 Web 端的 DOM 渲染,还可以用于 React Native 的原生组件渲染(语法一致,底层渲染不同);
- 工具支持:Babel、TypeScript、ESLint 等工具对 JSX 有完善的支持,提升开发效率。
六、总结
JSX 是 React 开发的基础,它不是 HTML,也不是新的编程语言,而是 JavaScript 的语法糖。掌握 JSX 的核心语法规则(如标签闭合、className、表达式插入)、进阶用法(如 Fragment、列表渲染、条件渲染)和避坑指南,是编写高效、可维护的 React 代码的关键。
值得一提的是,JSX 并非 React 的专属特性,Vue 3 也支持 JSX 语法,甚至一些其他前端框架也开始兼容 JSX。因此,学好 JSX 不仅能提升 React 开发能力,也是前端工程师的通用技能。
最后,记住:JSX 的本质是 JavaScript,所有 JavaScript 的特性都可以与 JSX 结合使用。不要被类 HTML 的语法迷惑,始终以 JavaScript 的思维来编写 JSX。