手写 Mini React:从 0 实现 createElement 和 render,理解 React 的底层原理

在学习 React 的过程中,很多人都会有几个疑问:

  • JSX 为什么可以写在 JavaScript 里面?
  • React 的 Virtual DOM 到底是什么?
  • React 为什么不直接操作 DOM?
  • React 渲染 UI 的流程是怎样的?

如果只是使用 useStateuseEffect 等 API,很难真正理解 React 的核心设计。

最好的学习方式其实是:自己实现一个 Mini React

React 官方教程中有一个经典项目叫 Didact,它是一个极简版 React,用几十行代码实现 React 的核心机制。

本文将通过实现一个简单的 Mini React,理解 React 的核心流程:

sql 复制代码
JSX
 ↓
createElement
 ↓
Virtual DOM
 ↓
render
 ↓
Real DOM

通过这个过程,你会更清晰地理解 React 的底层原理。


一、React 的核心思想

现代前端框架(React / Vue)普遍遵循一种架构模式:

MVVM

sql 复制代码
Model(数据)
   ↓
ViewModel(框架)
   ↓
View(DOM)

开发者只需要关注 数据和业务逻辑,DOM 的创建、更新和销毁都由框架处理。

React 的核心思想可以总结为一句话:

用 JavaScript 描述 UI

例如传统 DOM 写法:

ini 复制代码
const div = document.createElement("div")
div.innerText = "Hello"
document.body.appendChild(div)

React 写法:

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

React 会把 UI 转换成一种 JavaScript 数据结构,再由框架生成真实 DOM。


二、JSX 的本质

很多初学者会认为 JSX 是 HTML,但实际上 JSX 只是 JavaScript 的语法糖

例如:

javascript 复制代码
const element = <h1>Hello</h1>

经过 Babel 编译后会变成:

csharp 复制代码
const element = React.createElement("h1", null, "Hello")

所以 JSX 最终都会转换成一个函数调用:

scss 复制代码
createElement(type, props, children)

这也是 React 最核心的 API。


三、实现 createElement

接下来我们实现一个简化版 createElement

typescript 复制代码
function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === "object"
          ? child
          : createTextElement(child)
      )
    }
  }
}

这个函数返回的是一个 Virtual DOM 对象

例如:

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

会被转换成:

css 复制代码
{
  type: "div",
  props: {
    children: [
      {
        type: "TEXT_ELEMENT",
        props: {
          nodeValue: "Hello",
          children: []
        }
      }
    ]
  }
}

可以看到,Virtual DOM 本质上只是普通 JavaScript 对象


四、为什么需要 TEXT_ELEMENT

在真实 DOM 中,其实存在两种节点:

scss 复制代码
Element Node
Text Node

例如:

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

DOM 结构实际上是:

less 复制代码
div
 └── textNode("Hello")

为了统一处理节点结构,React 会把文本也包装成一个对象。

arduino 复制代码
function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: []
    }
  }
}

这样所有节点都会变成统一的数据结构:

bash 复制代码
{
  type,
  props
}

这也是 Virtual DOM 设计的重要原因:
统一数据结构,方便后续算法处理。


五、实现 render(VDOM → DOM)

现在我们已经得到了 Virtual DOM,接下来要把它转换成真实 DOM。

实现一个 render 函数:

ini 复制代码
function render(element, container) {
  const dom =
    element.type === "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(element.type)

  const isProperty = key => key !== "children"

  Object.keys(element.props)
    .filter(isProperty)
    .forEach(name => {
      dom[name] = element.props[name]
    })

  element.props.children.forEach(child =>
    render(child, dom)
  )

  container.appendChild(dom)
}

这个函数主要完成三件事情。

1 创建 DOM 节点

javascript 复制代码
document.createElement()
document.createTextNode()

如果是普通节点就创建 Element,如果是文本节点就创建 TextNode


2 设置属性

css 复制代码
dom[name] = element.props[name]

例如:

ini 复制代码
<h1 id="title">

最终会变成:

ini 复制代码
dom.id = "title"

3 递归渲染子节点

DOM 本身是树结构:

css 复制代码
div
 ├─ h1
 └─ h2

所以必须递归创建子节点:

ini 复制代码
element.props.children.forEach(child =>
  render(child, dom)
)

最终整个 Virtual DOM 树都会被转换成真实 DOM。


六、完整 Mini React 使用

最后导出 API:

ini 复制代码
window.Didact = {
  createElement,
  render
}

就可以写 JSX:

javascript 复制代码
/** @jsxRuntime classic */
/** @jsx Didact.createElement */

const element = (
  <div style="background:salmon">
    <h1>Hello, world!</h1>
    <h2 style="text-align:right">from Didact</h2>
  </div>
)

const container = document.getElementById("root")
Didact.render(element, container)

浏览器最终生成真实 DOM:

xml 复制代码
<div style="background:salmon">
  <h1>Hello, world!</h1>
  <h2 style="text-align:right">from Didact</h2>
</div>

至此,我们已经完成了一个最简单的 Mini React


七、React 为什么需要 Virtual DOM?

很多人认为 Virtual DOM 的目的就是"更快",其实并不完全准确。

Virtual DOM 的真正价值在于:

  • 抽象 UI
  • 可预测更新
  • 方便比较差异

React 会通过 Diff 算法 对比新旧 Virtual DOM:

复制代码
旧 VDOM
   vs
新 VDOM

只更新发生变化的部分,从而减少真实 DOM 操作。

例如:

css 复制代码
旧
<li>A</li>
<li>B</li>

新
<li>A</li>
<li>C</li>

React 只会更新:

css 复制代码
B → C

而不是重新创建整个列表。


八、总结

通过实现一个 Mini React,我们可以清晰地看到 React 的核心流程:

sql 复制代码
JSX
 ↓
createElement
 ↓
Virtual DOM
 ↓
render
 ↓
Real DOM

整个过程本质上就是:

用 JavaScript 对象描述 UI,再转换成真实 DOM。

当然,真正的 React 还包含很多复杂机制,例如:

  • Diff 算法
  • Fiber 架构
  • Hooks
  • 组件系统
  • 并发渲染

但所有这些能力,都是建立在 Virtual DOM 这一核心思想之上

理解了这个过程,再去阅读 React 源码,就会容易很多。

相关推荐
爱滑雪的码农11 小时前
详细说说React大型项目结构以及日常开发核心语法
前端·javascript·react.js
@大迁世界12 小时前
43.HTML 事件处理和 React 事件处理有什么区别?
前端·javascript·react.js·html·ecmascript
@大迁世界13 小时前
41.ShadCN 是什么?它如何和 Tailwind CSS 集成,从而更容易构建可访问且可自定义的 React 组件?
前端·javascript·css·react.js·前端框架
费曼学习法16 小时前
React 18 并发模式(Concurrent Mode):Fiber 架构的终极进化
javascript·react.js
gogoing20 小时前
React 分包加载优化
前端·react.js
openKaka_1 天前
beginWork 的第一站:HostRoot 如何把 App 接入 Fiber 树
前端·javascript·react.js
孟祥_成都1 天前
前端唯一的护城河?结合 AI 将字节组件库 Headless 化后的感想~
前端·人工智能·react.js
不爱学英文的码字机器1 天前
被 AE 的关键帧折磨过的人,应该试试这个用 React 写视频的路子
前端·react.js·音视频
不会写DN1 天前
为什么需要 @types/react? 解决“无法找到模块 react 的声明文件”报错
前端·react.js·前端框架