Appearance
JSX 与 React Element
我们通常使用 JSX 来描述 UI 的外观。然而,JSX 并非浏览器能够直接理解的 JavaScript 代码。本章将深入探讨 JSX 的本质,它如何被编译为 jsx 或 jsxs 调用,以及这些调用所产生的核心数据结构——React Element。我们还将了解 React Element 的内部结构及其在 React 渲染流程中的关键作用,并初步探讨它如何转化为 Fiber 节点。
JSX 语法糖
JSX 是一种 JavaScript 的语法扩展,它允许我们在 JavaScript 代码中书写类似 HTML 的标签。这使得 UI 的描述变得直观且富有表现力。但实际上,JSX 只是一种语法糖,它在编译时会被转换为普通的 JavaScript 函数调用。
Client Component
例如,以下 JSX 代码:
jsx
const element = <h1 className="greeting">Hello, world!</h1>;在构建过程中由构建工具(如 Babel,通过 @babel/plugin-transform-react-jsx 插件,并配置 { "runtime": "automatic" } ;或 SWC)自动完成的,会被编译成普通的 JavaScript 函数调用。
在 React 17 之前,客户端组件中的JSX会被编译为 React.createElement:
javascript
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello, world!'
);从 React 17 开始,为了实现新的 JSX Transform,JSX 编译不再需要引入 React 库,而是直接引入 react/jsx-runtime 中的 jsx 或 jsxs 函数。这使得编译后的代码更小,并且在未来可以实现更灵活的优化。在 React 19 中,这一机制得到了进一步的巩固和优化。
因此,上述 JSX 代码在react19中会被编译为:
javascript
// 假设在文件顶部隐式引入了 jsx 运行时
import { jsx } from 'react/jsx-runtime';
const element = jsx(
'h1',
{ className: 'greeting', children: 'Hello, world!' }
);对于包含多个子元素的 JSX,例如:
jsx
const list = (
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
);它会被编译为 jsxs(JSX with static children)函数调用,以优化静态子元素的性能:
javascript
// 假设在文件顶部隐式引入了 jsxs 运行时
import { jsxs } from 'react/jsx-runtime';
const list = jsxs(
'ul',
{ children: [jsx('li', { children: 'Item 1' }), jsx('li', { children: 'Item 2' })] }
);Server Component
在服务器组件 中,JSX 同样被使用,但其编译和处理流程不同。服务器组件执行在服务器端,它们返回的 JSX(或其结果)会被序列化成一种特殊的流式格式(通常称为 RSC Payload),然后发送到客户端。
客户端接收到 RSC Payload 后,React 会解析它并在客户端“物化”出相应的 UI,可能包括 HTML 结构和用于交互的 Client Components 的“指令”。 具体的流程将会在后续的SSR章节中详细的讲解。
无论是 React.createElement 还是 jsx/jsxs,它们的核心目的都是相同的:创建一个描述 UI 结构和属性的纯 JavaScript 对象。这个对象就是 React Element。
React Element
React Element 是 React 应用中最小的构建块。它是一个轻量级的、不可变的纯 JavaScript 对象,用于描述你希望在屏幕上看到什么。它不是真实的 DOM 节点,也不是组件实例,而仅仅是一个“描述”对象。
一个典型的 React Element 对象结构如下:
javascript
{
$$typeof: Symbol.for('react.element'), // 唯一标识,防止 XSS 攻击
type: 'h1', // 元素类型:字符串(DOM 标签)或函数/类(组件)
key: null, // 用于列表渲染的唯一标识
ref: null, // 用于获取 DOM 实例或组件实例的引用
props: { // 元素的属性,包括 children
className: 'greeting',
children: 'Hello, world!'
},
_owner: null, // 内部属性,指向创建该 Element 的 Fiber
_store: {}, // 内部属性,用于开发模式下的检查
// ... 其他内部属性,如 _source, _self 等,主要用于开发模式和调试
}React Element 各属性的含义:
$$typeof: 这是一个Symbol类型的值,Symbol.for('react.element')。它的主要作用是作为一种安全机制,防止 XSS 攻击。因为Symbol类型的值不能被 JSON 序列化,所以恶意代码无法通过 JSON 注入来伪造React Element对象。type: 表示元素的类型。它可以是:- 字符串: 对于原生 DOM 元素,
type是一个字符串,例如'div'、'span'、'h1'等。 - 函数或类: 对于 React 组件,
type是组件的函数或类本身,例如App、MyButton等。
- 字符串: 对于原生 DOM 元素,
key: 一个可选的字符串,用于在列表渲染时帮助 React 识别哪些项发生了变化、被添加或被删除。key在 Diff 算法中扮演着至关重要的角色,它能显著提高列表更新的性能。ref: 一个可选的属性,用于获取对底层 DOM 节点实例或类组件实例的引用。在函数组件中,通常使用useRefHook 来实现类似的功能。props: 一个包含所有属性(包括children)的普通 JavaScript 对象。children属性可以是一个字符串、一个 React Element、一个数组(包含多个 React Element),甚至是null或undefined。_owner、_store等内部属性:这些属性主要用于 React 内部的调试、优化和错误检查,在生产环境中通常会被移除或简化。
React Element 的作用:
React Element 是 React 协调阶段的输入。它描述了 UI 在某个特定时间点的理想状态。React 会根据这些 React Element 对象来构建或更新其内部的 Fiber 树,并最终将其渲染到真实 DOM 上。
- 描述性: 它只描述了“应该是什么”,而不是“如何去做”。
- 不可变性: 一旦创建,
React Element就不能被修改。每次更新都会生成新的React Element对象。 - 轻量级: 它们是简单的 JavaScript 对象,创建成本非常低,这使得 React 可以频繁地创建它们而不会造成性能负担。
React Element -> Fiber 节点
虽然 React Element 是 UI 的描述,但 React 内部真正进行协调和渲染工作的是 Fiber 节点。当 React 接收到 React Element 时(例如在 beginWork 阶段),它会将其转换为对应的 Fiber 节点。
在react/packages/react-reconciler/src/ReactFiber.js中的createFiberFromElement函数, 实现了将 React Element 转换为 Fiber 节点的逻辑。 通过 createFiberFromElement 函数实现,该函数从 React Element 中提取 type、key、props 等信息,然后调用 createFiberFromTypeAndProps 创建对应的 Fiber 节点。
例如,对于一个 <h1> 元素:
javascript
const element = jsx('h1', { className: 'greeting', children: 'Hello, world!' });React 会创建一个 HostComponent 类型的 Fiber 节点,其 type 为 'h1',pendingProps 包含 { className: 'greeting', children: 'Hello, world!' }。如果这是一个自定义组件,例如 <App />,则会创建一个 FunctionComponent 或 ClassComponent 类型的 Fiber 节点,其 type 为 App 函数或类本身。
React Element 与 Fiber 节点的区别:
React Element: 描述 UI 的“意图”,是轻量级的、不可变的纯对象。Fiber节点: 描述 UI 的“当前状态”和“工作进度”,是可变的、包含更多运行时信息的内部数据结构,用于驱动协调和渲染过程。
理解 React Element 是理解 React 内部工作原理的第一步。它是连接我们编写的 JSX 代码与 React 内部复杂协调机制的桥梁。通过将声明式的 JSX 转换为结构化的 React Element 对象,React 能够高效地追踪 UI 的变化,并最终将其呈现在屏幕上。
