JSX vs React.createElement:两种写法,一个底层原理

151 阅读6分钟

📌 引言

在 React 生态系统中,JSX(JavaScript XML) 是最核心、最标志性的语法特性之一。它允许开发者在 JavaScript 文件中直接书写类似 HTML 的结构,极大地提升了 UI 构建的可读性和开发效率。

但 JSX 并不是浏览器原生支持的语法,也不是标准的 JavaScript。它背后隐藏着一套复杂的编译流程和运行时机制。本文将带你从 JSX 的语法形式 出发,深入探讨其 编译过程、底层实现原理、虚拟 DOM 构建机制,并结合一个完整的 React 组件示例,逐行分析代码逻辑,最后重点对比两种不同方式定义 React 元素的区别。


🧩 一、什么是 JSX?

JSX(JavaScript XML) 是一种 JavaScript 的语法扩展,允许我们在 JavaScript 文件中书写类似 HTML 的结构:

const element = <h1>Hello, JSX!</h1>;

尽管它看起来像 HTML,但实际上它是一种 语法糖,最终会被编译成标准的 JavaScript 函数调用,即 React.createElement()


⚙️ 二、JSX 的编译流程

1. JSX 代码

<h1 className="title">Hello, world</h1>

2. Babel 编译后

React.createElement('h1', { className: 'title' }, 'Hello, world');

3. 编译器:Babel 的作用

  • Babel 是一个 JavaScript 编译器,负责将 ES6+、JSX 等新语法转换为浏览器兼容的旧版本 JavaScript。
  • 对于 JSX,Babel 会调用 @babel/plugin-transform-react-jsx 插件,将 JSX 转换为 React.createElement() 调用。

4. React 17+ 的新特性:Runtime 自动引入

从 React 17 开始,你不再需要手动 import React from 'react',因为 Babel 会自动引入一个 新的 JSX 转换运行时,例如:

// 你写的
const element = <h1>Hello</h1>;

// Babel 实际生成
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx("h1", { children: "Hello" });

这使得 JSX 更加轻量,并支持更多框架使用 JSX 语法。


🧱 三、React.createElement 的工作原理

1. 函数签名

React.createElement(type, [props], [...children])

参数详解:

参数说明
type元素类型,可以是字符串(如 'div')、React 组件(函数或类)
props属性对象,包括 HTML 属性、事件监听器、keyref
children子元素,可以是字符串、数字、React 元素,也可以是数组

2. 返回值:虚拟 DOM 对象

React.createElement() 返回一个描述 DOM 节点的 JavaScript 对象,即所谓的 虚拟 DOM(Virtual DOM)

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

这个对象会被 React 用来与旧的虚拟 DOM 做 diff 算法,计算出最小的 DOM 更新操作。


🧪 四、完整代码解析:App 组件详解

我们来看一个完整的 React 组件示例,逐步解析其结构和运行机制。

import { useState, createElement } from 'react'
import './App.css'

function App() {
  const [todos, setTodos] = useState([
    { id: 1, title: '标题一' },
    { id: 2, title: '标题二' },
    { id: 3, title: '标题三' }
  ])

  const element = <h1 className='title'>Hello,world</h1>
  const elememt2 = createElement('h1', { className: 'title', id: 'tit' }, 'Hello,world')

  return (
    <>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
      <ul>
        {todos.map((todo) => (
          createElement('li', { key: todo.id }, todo.title)
        ))}
      </ul>
      {element}
      {elememt2}
    </>
  )
}

export default App

🔍 1. 状态管理:useState

const [todos, setTodos] = useState([...])
  • 使用 useState 定义了一个状态变量 todos,表示待办事项列表。
  • 初始化了三个待办事项对象,每个都有 idtitle
  • React 会自动跟踪该状态的变化,并在变化时重新渲染组件。

🔍 2. JSX 元素定义

const element = <h1 className='title'>Hello,world</h1>
  • 使用 JSX 定义了一个标题元素。
  • 通过 className 设置样式类名。
  • 实际上会被编译为:
React.createElement('h1', { className: 'title' }, 'Hello,world')
const elememt2 = createElement('h1', { className: 'title', id: 'tit' }, 'Hello,world')
  • 显式使用 React.createElement() 创建元素。
  • 除了 className,还额外添加了 id="tit" 属性。
  • 适用于需要动态构建元素属性的场景。

🔍 3. 渲染部分详解

第一组 <ul>:使用 JSX 渲染列表

<ul>
  {todos.map((todo) => (
    <li key={todo.id}>{todo.title}</li>
  ))}
</ul>
  • 使用 JSX 的 <li> 标签渲染每个待办事项。
  • key 是 React 用于优化渲染的唯一标识符,必须唯一。

(关于key相关内容可以返回阅读上一篇博客)

第二组 <ul>:使用 createElement 渲染列表

<ul>
  {todos.map((todo) => (
    createElement('li', { key: todo.id }, todo.title)
  ))}
</ul>
  • 使用 createElement() 手动创建 <li> 元素。
  • 功能与第一组完全一致,但写法更底层。

最后渲染两个标题元素

{element}
{elememt2}
  • element 是 JSX 创建的标题。
  • elememt2createElement 创建的标题。
  • 两者都会被 React 正确渲染为 <h1> 元素。

📊 五、JSX 与 createElement() 的对比分析

特性JSXReact.createElement()
语法风格类似 HTML,更直观函数式写法,更底层
可读性高,适合 UI 结构较低,适合动态逻辑
灵活性适合静态结构更适合动态生成元素
是否需要编译是(需 Babel)
是否支持属性扩展支持(如 key, className完全支持,可自由控制
适用场景UI 构建、组件结构动态元素生成、高阶组件、插件开发

✅ 示例对比

// JSX
const element = <h1 className="title">Hello, world</h1>

// 编译后
const element = React.createElement('h1', { className: 'title' }, 'Hello, world')
// createElement
const elememt2 = React.createElement('h1', { className: 'title', id: 'tit' }, 'Hello, world')

两者最终都会被 React 渲染为相同的虚拟 DOM 对象,但在开发阶段,JSX 更加直观和简洁。


🧠 六、虚拟 DOM 的构建与更新机制

1. 虚拟 DOM 的构建

React 通过 React.createElement() 创建的虚拟 DOM 对象构建一棵虚拟 DOM 树。例如:

const vdom = React.createElement('ul', null,
  React.createElement('li', { key: 1 }, '标题一'),
  React.createElement('li', { key: 2 }, '标题二'),
  React.createElement('li', { key: 3 }, '标题三')
);

这个虚拟 DOM 树会被 React 用来与上一次的虚拟 DOM 进行比较,计算出最小的 DOM 更新操作。

2. Diff 算法

React 使用高效的 diff 算法 对比新旧虚拟 DOM:

  • 层级对比:只对比同一层级的节点。
  • 列表 key 优化:通过 key 属性识别元素的唯一性,提升列表更新效率。
  • 最小化 DOM 操作:只更新真正变化的部分,避免全量重绘。

3. 真实 DOM 的更新

一旦 React 确定需要更新的节点,它会通过原生的 document.createElement()appendChild()removeChild() 等方法更新真实 DOM。


🎨 七、总结与建议

  • JSX 是 React 的核心语法,它让 UI 开发变得直观、高效。
  • React.createElement() 是 JSX 的底层实现方式,适用于更底层的开发场景。
  • 在日常开发中推荐使用 JSX,但在高阶组件、动态渲染、插件开发等场景中,createElement 会更有优势。
  • 理解 JSX 的编译过程和底层原理,有助于写出更高效、更清晰的 React 代码。
  • React 的虚拟 DOM 和 diff 算法是其性能优化的关键,理解其机制有助于我们写出更高效的组件。

📄 结语

通过本文,我们不仅了解了 JSX 的基本概念和底层原理,还通过实际代码深入对比了 JSX 与 React.createElement() 的异同。无论你是初学者还是有经验的开发者,理解这些内容都将帮助你更好地掌握 React 开发的精髓。

欢迎留言交流你对 JSX 的看法,或者分享你在项目中使用 JSX 的最佳实践!