Skip to content

JSX 与 React Element

我们通常使用 JSX 来描述 UI 的外观。然而,JSX 并非浏览器能够直接理解的 JavaScript 代码。本章将深入探讨 JSX 的本质,它如何被编译为 jsxjsxs 调用,以及这些调用所产生的核心数据结构——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 中的 jsxjsxs 函数。这使得编译后的代码更小,并且在未来可以实现更灵活的优化。在 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 是组件的函数或类本身,例如 AppMyButton 等。
  • key: 一个可选的字符串,用于在列表渲染时帮助 React 识别哪些项发生了变化、被添加或被删除。key 在 Diff 算法中扮演着至关重要的角色,它能显著提高列表更新的性能。
  • ref: 一个可选的属性,用于获取对底层 DOM 节点实例或类组件实例的引用。在函数组件中,通常使用 useRef Hook 来实现类似的功能。
  • props: 一个包含所有属性(包括 children)的普通 JavaScript 对象。children 属性可以是一个字符串、一个 React Element、一个数组(包含多个 React Element),甚至是 nullundefined
  • _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 />,则会创建一个 FunctionComponentClassComponent 类型的 Fiber 节点,其 typeApp 函数或类本身。

React ElementFiber 节点的区别:

  • React Element: 描述 UI 的“意图”,是轻量级的、不可变的纯对象。
  • Fiber 节点: 描述 UI 的“当前状态”和“工作进度”,是可变的、包含更多运行时信息的内部数据结构,用于驱动协调和渲染过程。

理解 React Element 是理解 React 内部工作原理的第一步。它是连接我们编写的 JSX 代码与 React 内部复杂协调机制的桥梁。通过将声明式的 JSX 转换为结构化的 React Element 对象,React 能够高效地追踪 UI 的变化,并最终将其呈现在屏幕上。


微信公众号二维码