🌸 欢迎访问我的个人博客: 百里静修的花园
什么是 JSX?
JSX 是 JavaScript 的一种语法扩展,既然是语法扩展,就意味着 JavaScript 语法本身是不支持 JSX 的,那么如何在浏览器中渲染 JSX 呢?打包工具(如 webpack)会使用转译工具(一般是 Babel)将 JSX 转换为函数调用
JSX 语法基础
基本语法结构
这种混合了 JavaScript 和 HTML 的语法就是 JSX。在编译过程中,JSX 会被转换为普通的 JavaScript 函数调用,最终生成 React 元素。
- code.jsx
const element = <h1>Hello, world!</h1>;
在 JSX 中嵌入表达式
你可以在 JSX 中使用花括号{}嵌入任何有效的 JavaScript 表达式:
- case1.jsx
const name = "Josh Perez";
const element = <h1>Hello, {name}</h1>;
- case2.jsx
const element = <img src={user.avatarUrl}></img>;
JSX 本身也是表达式
在编译后,JSX 表达式会被转为普通 JavaScript 对象,这意味着你可以在if语句和for循环中使用 JSX,将它赋值给变量,或者作为函数的参数和返回值:
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
JSX 最佳实践
多行 JSX
对于多行 JSX 表达式,建议使用括号包裹以避免自动分号插入的陷阱:
- correct.jsx
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
// JavaScript解析器可能会在第一行的const element = <div>后自动插入一个分号
const element =
<div>;
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
标签必须闭合
JSX 要求标签必须正确闭合,自闭合标签必须以/>结尾:
<img src="path/to/image.jpg" alt="Description" />
<input type="text" name="username" />
className 而非 class
由于 JSX 是 JavaScript 的语法扩展,而class是 JavaScript 的保留字,所以在 JSX 中使用className来指定 CSS 类:
<div className="container">Content here</div>
htmlFor 而非 for
由于for是 JavaScript 的保留字,所以在 JSX 中 label 标签使用htmlFor来指定 HTML 的for属性:
<label htmlFor="username">Username</label>
React 中使用 JSX
style 属性
在 React 中,style属性的值是一个 JavaScript 对象,而不是 HTML 的style属性。可以动态地设置样式属性。
const element = (
<div
style={{
color: "red",
fontSize: "16px",
}}
>
Hello
</div>
);
JavaScript 变量类型
- 字符串
- 数字
- null/undefined/布尔值不会被渲染
- example.jsx
// 布尔值无论是什么,都不会被渲染,方便条件渲染
const flag = false;
const element = <div>{flag && "Hello"}</div>;
// 输出:
// <div></div>
- 数组-展开每一项
<div>
{["a", "b", "c"].map((item) => (
<div key={item}>{item}</div>
))}
</div>
等同于
<div>
<div key="a">a</div>
<div key="b">b</div>
<div key="c">c</div>
</div>
JSX 防注入攻击
React DOM 在渲染所有输入内容之前,默认会进行转义,这有助于防止 XSS(跨站脚本)攻击。
- 转义 是将输入内容中的特殊字符
(如 <, >, &)转换为对应的 HTML 实体编码,使浏览器将其视为普通文本而非可执行代码。React 默认对所有动态内容进行转义,从根本上阻断 XSS 攻击的可能。 - 如果需要显示富文本(如用户输入的 HTML 内容),必须 显式告知 React 允许渲染原始 HTML,但需严格过滤内容以确保安全。使用
dangerouslySetInnerHTML
- transform.jsx
// 用户输入恶意内容
const userInput = "<script>alert('XSS');</script>";
function App() {
return <div>{userInput}</div>;
}
// 渲染后的 HTML:
// <div><script>alert('XSS');</script></div>
- dangerous.jsx
const sanitizedHTML = { __html: "<b>安全内容</b>" };
function App() {
return <div dangerouslySetInnerHTML={sanitizedHTML} />;
}
JSX 转译
React17 之前的版本
Babel 会把 JSX 转译成React.createElement()函数调用:需要 import React from 'react'
const element = (
<h1 className="greeting">
<div>Hello, world!</div>
<div>Good to see you here.</div>
</h1>
);
等价于: Babel 转译
// 第一个参数是标签名,第二个参数是属性对象,后续参数是子节点
const element = React.createElement(
"h1",
{
className: "greeting",
},
React.createElement("div", null, "Hello, world!"),
React.createElement("div", null, "Good to see you here.")
);
React.createElement()会创建一个类似下面的对象(称为“React 元素”):虚拟 DOM 对象
// 注意:这是简化过的结构
const element = {
type: "h1",
props: {
className: "greeting",
children: [
{ type: "div", props: { children: "Hello, world!" } },
{ type: "div", props: { children: "Good to see you here." } },
],
},
key: null,
ref: null,
};
React17 及之后的版本
-
解耦 React 全局依赖:不再需要手动导入 React。在旧版转换中,即使你没有直接使用
React变量,也必须导入它,因为 JSX 会被转换为React.createElement调用。新的转换自动从react/jsx-runtime导入所需函数,无需显式导入 React。 -
打包体积优化:新的实现减少了些许打包体积
-
函数参数:子元素作为
props.children传递,第三个参数提取props.key
// 用户编写的 JSX
function App() {
return (
<div key="key" ref="ref">
<div>Hello World</div>
<div>Good to see you here.</div>
</div>
);
}
// Babel 转换后的代码(React 17+)
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
function App() {
return _jsxs(
"div",
{
ref: "ref",
children: [
_jsx("div", {
children: "Hello World",
}),
_jsx("div", {
children: "Good to see you here.",
}),
],
},
"key"
);
}
手写 JSX 转换函数
函数的封装
- createElement.js
const REACT_ELEMENT = Symbol("react.element");
const excludeProps = ["key", "ref", "__self", "__source"];
function createElement(type, props, ...children) {
props = props || {};
let ref = props.ref || null;
let key = props.key || null;
excludeProps.forEach((key) => {
delete props[key];
});
props.children = null;
if (children?.length >= 1) {
props.children = children.map((child) => child);
}
return {
$$typeof: REACT_ELEMENT,
type,
props,
key,
ref,
};
}
- jsx.js
function jsx(type, config, key) {}
总结
JSX 是 React 的核心部分,它简化了 UI 组件的创建和维护。通过将标记语法和 JavaScript 逻辑结合,JSX 使得 React 组件的开发变得更加直观和高效。