React JSX 转换原理与 GSR 实现解析

React JSX 转换原理与 GSR 实现解析

本文是我在学习 React JSX 转换原理及其底层实现机制时的笔记与理解,主要涉及 GSR(即自定义的 createElement 实现)、Babel 编译机制、React 包结构及 JSX 的运行逻辑。通过梳理 JSX → JS → DOM 的全过程,深入理解 React 声明式编程的本质。


一、React 项目结构与包关系

在 React 的源码架构中,各个包之间分工明确:

  • react
    提供与运行环境无关的通用方法和数据结构(例如 ReactElement 的定义与创建)。
    可以理解为 React 的"核心逻辑层"。
  • react-reconciler
    负责协调(reconcile)虚拟 DOM 树与真实 DOM 的差异。
    这是 React 更新机制的核心部分。
  • react-dom
    作为浏览器环境的实现层,将协调结果映射到实际的 DOM 操作上。
  • shared
    定义了通用类型与工具函数,供多个包复用。

这种拆分结构的优势在于:React 可以被用于浏览器、Native、甚至自定义渲染器中,而核心逻辑只需要维护一份。


二、JSX 是什么?为什么是 "XML" 语法糖?

1. JSX 并不是 HTML

很多初学者容易误会 JSX 是 HTML。

其实,JSX 只是 JavaScript 的一种语法扩展,让我们可以用更直观的方式声明 UI 结构。

例如:

ini 复制代码
const element = <h1 className="title">Hello React</h1>;

在编译后,会被 Babel 转换为:

ini 复制代码
const element = React.createElement("h1", { className: "title" }, "Hello React");

所以,JSX 最终都会变成 JavaScript 函数调用

2. 为什么说是 "XML-like"?

JSX 采用了与 XML 类似的标签结构(如 <tag attr="value">children</tag>),语法上接近 HTML,但并不等价于 HTML。

例如:

  • JSX 中的 class 必须写成 className
  • JSX 中可以嵌入任意 JS 表达式 {}
  • JSX 语义最终由 JS 决定,而非浏览器 DOM。

换句话说,JSX 是一种 "声明界面结构的 XML 语法糖"

它让我们可以像写 HTML 一样"声明"界面,但底层执行的仍是 JS 逻辑。

3. "语法糖"是什么意思?

"语法糖"(Syntactic Sugar)指的是让代码更易读、更易写的语法形式,但不会改变语言本身的功能。

举个例子:

css 复制代码
<div>Hello</div>

csharp 复制代码
React.createElement("div", null, "Hello")

两者最终效果一致,只是前者更"甜"一点(更直观),因此叫"语法糖"。


三、JSX 转换原理:编译时与运行时

JSX 的实现过程分为两个阶段:

1. 编译时(Babel 转换)

由 Babel 完成,将 JSX 转换为 JS 函数调用。

转换规则示例:

css 复制代码
<Button color="blue">Click</Button>

➡️ 编译后变为:

css 复制代码
React.createElement(Button, { color: "blue" }, "Click");

Babel 在这里扮演"语法翻译器"的角色。

2. 运行时(执行 GSR 或 React.createElement)

编译后的代码会调用运行时提供的 React.createElement 方法。

在自定义实现(如 GSR 转换)中,我们可以自定义该方法的行为。

示例实现:

csharp 复制代码
function GSR(type, config, ...children) {
  const props = { ...config };
  if (children.length > 0) {
    props.children = children.length === 1 ? children[0] : children;
  }
  return { type, key: props.key || null, ref: props.ref || null, props };
}

最终返回一个 ReactElement 对象

yaml 复制代码
{
  type: 'div',
  key: null,
  ref: null,
  props: { className: 'app', children: 'Hello React' }
}

这就是虚拟 DOM(Virtual DOM)的基本形态。


四、RedElement 与 RedSymbol 的设计

在 React 内部,虚拟 DOM(ReactElement)是以对象结构存在的。

项目中自定义实现的 RedElement 结构如下:

rust 复制代码
{
  $$typeof: Symbol('red.element'),
  type,
  key,
  ref,
  props
}
  • $$typeof:用于区分对象类型,防止滥用;
  • type:元素类型(如 'div' 或组件);
  • key:用于 Diff;
  • ref:用于访问 DOM 或组件;
  • props:组件属性。

为了确保每个元素的唯一性,引入了 RedSymbol

  • 如果环境支持 Symbol,就使用 Symbol('red.element')
  • 否则使用一个数字常量代替。

这样可以保证每个 ReactElement 在调试和识别时都是安全且唯一的。


五、React.createElement 的底层逻辑

React 的 createElement 实现与 GSR 几乎一致。

伪代码如下:

ini 复制代码
function createElement(type, config, ...children) {
  const props = {};
  let key = null;
  let ref = null;

  if (config) {
    key = config.key ?? null;
    ref = config.ref ?? null;
    for (const prop in config) {
      if (prop !== 'key' && prop !== 'ref') {
        props[prop] = config[prop];
      }
    }
  }

  if (children.length > 0) {
    props.children = children.length === 1 ? children[0] : children;
  }

  return ReactElement(type, key, ref, props);
}

这一过程说明:

  • JSX 的"声明式"结构只是表象;
  • React 内部依然是命令式 JS 函数调用
  • React 在运行时会再通过 reconciler 将这些虚拟节点映射为真实 DOM 操作。

六、React 17 之后 JSX 转换变化

React 17 之后引入了新的 JSX 转换方案。

即不再强制要求显式导入 React:

javascript 复制代码
// React 17 之前
import React from 'react';
const element = <div />;

// React 17 之后
const element = <div />; // 仍可正常工作

Babel 会自动引入新的运行时辅助函数 jsxjsxs,例如:

javascript 复制代码
import { jsx as _jsx } from 'react/jsx-runtime';
_jsx("div", { className: "box" });

这使得编译输出更简洁、性能更高。


七、总结与理解提升

  • JSX 是一种 声明式的语法糖,让我们以接近 HTML 的方式描述组件结构;
  • 它本质是 JavaScript 调用 React.createElement 的语法封装;
  • 编译阶段由 Babel 转换,运行阶段由 React 运行时(或自定义 GSR)解析;
  • React 底层依然是命令式的,通过协调器将虚拟 DOM 同步到真实 DOM;
  • React 17 之后引入了新的 JSX 转换机制,使开发体验更自然。

结语

理解 JSX 转换原理,就能更清楚地认识到 React 的核心哲学:

"表面是声明式的 JSX,实质是命令式的 JavaScript。"

这也是 React 高效、灵活的根本原因------它让开发者用声明的方式思考,但底层通过命令式逻辑精确地控制 DOM。

相关推荐
app出海创收老李3 小时前
海外独立创收日记(4)-第一笔汇款
前端·后端·程序员
苏打水com3 小时前
字节跳动前端业务:从「短视频交互」到「全球化适配」的技术挑战
前端·音视频
又是忙碌的一天3 小时前
前端学习 JavaScript
前端·javascript·学习
Jagger_3 小时前
Cursor + Apifox MCP:告别手动复制接口,AI 助你高效完成接口文档开发
前端
IT_陈寒4 小时前
Redis性能优化:5个被低估的配置项让你的QPS提升50%
前端·人工智能·后端
Hilaku4 小时前
重新思考CSS Reset:normalize.css vs reset.css vs remedy.css,在2025年该如何选?
前端·css·代码规范
袁煦丞4 小时前
一图看懂Docker管理 Portainer:cpoar内网穿透实验室第652个成功挑战
前端·程序员·远程工作
右子5 小时前
微信小程序开发“闭坑”指南
前端·javascript·微信小程序
入秋5 小时前
Three.js后期处理实战:噪点 景深 以及色彩调整
前端·javascript·three.js