人人都会AI编程

JSX 本质与编译原理

更新时间:2026-07-11

JSX 不是字符串模板,而是语法糖

JSX 看起来像在 JavaScript 中直接写 HTML,但它既不是字符串,也不是 HTML 模板。它的本质是 React.createElement() 函数的语法糖。浏览器无法直接执行 JSX,必须通过编译器(通常为 Babel 或 TypeScript)将其转换为标准的 JavaScript 函数调用。

const element = <h1 className="greeting">Hello, world!</h1>;

经过 Babel 编译后,会变成:

const element = React.createElement(
  'h1',
  { className: 'greeting' },
  'Hello, world!'
);

React.createElement 做了什么

React.createElement(type, props, ...children) 接受三个部分参数:

  • type:元素类型,可以是 HTML 标签名字符串(如 'div'),也可以是组件函数或类。
  • props:属性对象,包含所有的 HTML 属性和自定义属性,没有属性时传入 null
  • children:子元素,可以是字符串、数字、或者嵌套的 React.createElement 调用。

调用后返回一个普通的 JavaScript 对象,也就是虚拟 DOM 节点(React Element)

{
  $$typeof: Symbol(react.element),
  type: 'h1',
  key: null,
  ref: null,
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  },
  _owner: null
}

这个对象就是 React 用来描述 UI 结构的最小单元。React 在渲染时会读取这棵树,递归地将其转换为真实的 DOM 节点。

新版本的 JSX 转换(React 17+)

从 React 17 开始,React 引入了新的 JSX 转换,不再需要显式地在文件中 import React from 'react'。这是因为编译器不再将 JSX 编译为 React.createElement,而是自动从 react/jsx-runtime 中引入 jsxjsxs 函数。

// 源代码
const element = <h1>Hello</h1>;

// 新编译输出(自动插入 import)
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx('h1', { children: 'Hello' });

这个改动带来的实际好处是:

  • 不再需要手动引入 React:函数组件文件中可以省略 import React from 'react',代码更清爽。
  • 更好的 Tree Shaking:运行时模块可以更精细地裁剪未使用的功能。
  • 为未来优化铺路:编译器可以生成更精简的代码,减少打包体积。

目前主流的构建工具(Vite、Create React App、Next.js)都已经默认启用新的 JSX 转换。

编译原理的实践启示

理解 JSX 的本质有助于避免一些常见错误:

1. 为什么 JSX 中表达式必须用花括号包裹

因为花括号内部会被作为 JavaScript 表达式直接嵌入到 React.createElement 的参数中:

<p>{name}</p>  // → React.createElement('p', null, name)

2. 为什么条件渲染不能使用 if/else 语句

if/else 是语句,不是表达式,不能直接放在 JSX 的花括号内。必须使用三元表达式、逻辑与、或立即执行函数:

{isLoggedIn ? <Dashboard /> : <Login />}
{isLoading && <Spinner />}

3. 为什么组件名必须大写

编译器通过首字母大小写区分“HTML 标签”和“组件”:

  • 小写 → 字符串类型:<div>createElement('div')
  • 大写 → 变量引用:<MyComponent>createElement(MyComponent)

如果组件名小写,React 会将其当作原生 HTML 标签处理,导致渲染出未知的 DOM 节点(如 <mycomponent>)。

4. 为什么 JSX 的属性名使用驼峰命名

JSX 本质是 JavaScript 的语法糖,它的属性会直接作为 props 对象的键名。因此必须遵循 JavaScript 的标识符规范:className 而非 classonClick 而非 onclickhtmlFor 而非 for

手动实现一个简单的 createElement

为了加深理解,可以自己实现一个简化版:

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === 'object' ? child : createTextElement(child)
      ),
    },
  };
}

function createTextElement(text) {
  return {
    type: 'TEXT_ELEMENT',
    props: { nodeValue: text, children: [] },
  };
}

React 的真实实现更加复杂,包含了对 key、ref 的处理、性能优化和安全性检查,但核心思路完全一致:将 JSX 编译为嵌套的 JavaScript 对象,从而构建虚拟 DOM 树。