在学习 React 的过程中,很多人都会有几个疑问:
- JSX 为什么可以写在 JavaScript 里面?
- React 的 Virtual DOM 到底是什么?
- React 为什么不直接操作 DOM?
- React 渲染 UI 的流程是怎样的?
如果只是使用 useState、useEffect 等 API,很难真正理解 React 的核心设计。
最好的学习方式其实是:自己实现一个 Mini React。
React 官方教程中有一个经典项目叫 Didact,它是一个极简版 React,用几十行代码实现 React 的核心机制。
本文将通过实现一个简单的 Mini React,理解 React 的核心流程:
JSX
↓
createElement
↓
Virtual DOM
↓
render
↓
Real DOM
通过这个过程,你会更清晰地理解 React 的底层原理。
一、React 的核心思想
现代前端框架(React / Vue)普遍遵循一种架构模式:
MVVM
Model(数据)
↓
ViewModel(框架)
↓
View(DOM)
开发者只需要关注 数据和业务逻辑,DOM 的创建、更新和销毁都由框架处理。
React 的核心思想可以总结为一句话:
用 JavaScript 描述 UI
例如传统 DOM 写法:
const div = document.createElement("div")
div.innerText = "Hello"
document.body.appendChild(div)
React 写法:
<div>Hello</div>
React 会把 UI 转换成一种 JavaScript 数据结构,再由框架生成真实 DOM。
二、JSX 的本质
很多初学者会认为 JSX 是 HTML,但实际上 JSX 只是 JavaScript 的语法糖。
例如:
const element = <h1>Hello</h1>
经过 Babel 编译后会变成:
const element = React.createElement("h1", null, "Hello")
所以 JSX 最终都会转换成一个函数调用:
createElement(type, props, children)
这也是 React 最核心的 API。
三、实现 createElement
接下来我们实现一个简化版 createElement。
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child =>
typeof child === "object"
? child
: createTextElement(child)
)
}
}
}
这个函数返回的是一个 Virtual DOM 对象。
例如:
<div>Hello</div>
会被转换成:
{
type: "div",
props: {
children: [
{
type: "TEXT_ELEMENT",
props: {
nodeValue: "Hello",
children: []
}
}
]
}
}
可以看到,Virtual DOM 本质上只是普通 JavaScript 对象。
四、为什么需要 TEXT_ELEMENT
在真实 DOM 中,其实存在两种节点:
Element Node
Text Node
例如:
<div>Hello</div>
DOM 结构实际上是:
div
└── textNode("Hello")
为了统一处理节点结构,React 会把文本也包装成一个对象。
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: []
}
}
}
这样所有节点都会变成统一的数据结构:
{
type,
props
}
这也是 Virtual DOM 设计的重要原因:
统一数据结构,方便后续算法处理。
五、实现 render(VDOM → DOM)
现在我们已经得到了 Virtual DOM,接下来要把它转换成真实 DOM。
实现一个 render 函数:
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 节点
document.createElement()
document.createTextNode()
如果是普通节点就创建 Element,如果是文本节点就创建 TextNode。
2 设置属性
dom[name] = element.props[name]
例如:
<h1 id="title">
最终会变成:
dom.id = "title"
3 递归渲染子节点
DOM 本身是树结构:
div
├─ h1
└─ h2
所以必须递归创建子节点:
element.props.children.forEach(child =>
render(child, dom)
)
最终整个 Virtual DOM 树都会被转换成真实 DOM。
六、完整 Mini React 使用
最后导出 API:
window.Didact = {
createElement,
render
}
就可以写 JSX:
/** @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:
<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 操作。
例如:
旧
<li>A</li>
<li>B</li>
新
<li>A</li>
<li>C</li>
React 只会更新:
B → C
而不是重新创建整个列表。
八、总结
通过实现一个 Mini React,我们可以清晰地看到 React 的核心流程:
JSX
↓
createElement
↓
Virtual DOM
↓
render
↓
Real DOM
整个过程本质上就是:
用 JavaScript 对象描述 UI,再转换成真实 DOM。
当然,真正的 React 还包含很多复杂机制,例如:
- Diff 算法
- Fiber 架构
- Hooks
- 组件系统
- 并发渲染
但所有这些能力,都是建立在 Virtual DOM 这一核心思想之上。
理解了这个过程,再去阅读 React 源码,就会容易很多。