什么是 Virtual DOM?
Virtual DOM 是一种编程概念。在这个概念里, UI 以一种理想化的,或者说"虚拟的"表现形式被保存于内存中,并通过如 ReactDOM 等类库使之与"真实的" DOM 同步。这一过程叫做协调。
js
使用"虚拟的"表现形式被保存与内存中,其实在我们写代码的过程中很常见,也经常会用到。
就比如之前流行的低代码,如果是你,你会怎么实现。
💡 用低代码平台理解Virtual DOM
想象你要实现一个低代码平台,用户通过拖拽组件来构建页面。你会怎么设计?
// 用户拖拽后,我们在内存中保存这样的数据结构
const pageConfig = {
type: 'div',
props: { className: 'container' },
children: [
{
type: 'h1',
props: { style: { color: 'blue' } },
children: ['欢迎使用低代码平台']
},
{
type: 'button',
props: {
onClick: () => alert('点击了按钮'),
className: 'btn-primary'
},
children: ['点击我']
}
]
}
这个pageConfig
就是"虚拟的"表现形式!我们把UI的结构和状态保存在内存中,然后通过渲染引擎将其转换为真实的DOM。
js
// 渲染函数:将虚拟结构转换为真实DOM
function render(vnode, container) {
if (typeof vnode === 'string') {
return document.createTextNode(vnode);
}
const element = document.createElement(vnode.type);
// 设置属性
Object.keys(vnode.props || {}).forEach(key => {
if (key === 'onClick') {
element.addEventListener('click', vnode.props[key]);
} else if (key === 'className') {
element.className = vnode.props[key];
} else if (key === 'style') {
Object.assign(element.style, vnode.props[key]);
}
});
// 递归渲染子元素
(vnode.children || []).forEach(child => {
element.appendChild(render(child, element));
});
return element;
}
// 使用
const realDOM = render(pageConfig, document.body);
document.body.appendChild(realDOM);
这就是Virtual DOM的核心思想:用JavaScript对象描述UI结构,然后通过程序将其转换为真实DOM。
📝 常见的"虚拟化"场景
其实在开发中,我们经常使用这种"虚拟化"的思想:
JSON配置驱动的表单
js
const formConfig = {
fields: [
{ type: 'input', name: 'username', label: '用户名', required: true },
{ type: 'select', name: 'gender', label: '性别', options: ['男', '女'] }
]
}
路由配置
js
const routes = [
{ path: '/home', component: Home },
{ path: '/user/:id', component: UserDetail }
]
数据库ORM
js
const user = {
name: '张三',
age: 25,
email: 'zhangsan@example.com'
}
// 最终转换为SQL: INSERT INTO users (name, age, email) VALUES (...)
这些都是将抽象的配置或数据结构转换为具体实现的例子。
Virtual DOM 是一种 以 JS 对象的形式 表达页面结构的方式,本质上是用 JavaScript 模拟 DOM 结构的"影子副本",对真实DOM进行映射与管理。
这种方式赋予了 React 声明式的 API:您告诉 React 希望让 UI 是什么状态,React 就确保 DOM 匹配该状态。这使您可以从属性操作、事件处理和手动 DOM 更新这些在构建应用程序时必要的操作中解放出来。
js
💡 注解:这里说的"声明式API"是相对于"命令式"而言的。
命令式:告诉程序"怎么做"
function updateUserList(users) {
const list = document.getElementById('user-list');
list.innerHTML = ''; // 先清空
users.forEach(user => {
const li = document.createElement('li');
li.textContent = user.name;
li.addEventListener('click', () => showUserDetail(user.id));
list.appendChild(li);
});
}
声明式:告诉程序"要什么"
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<li key={user.id} onClick={() => showUserDetail(user.id)}>
{user.name}
</li>
))}
</ul>
);
}
声明式让我们专注于"最终要什么样的UI",而不用关心"如何一步步操作DOM来达到这个效果"。
React会自动处理DOM的创建、更新、删除等操作,我们只需要描述每个状态下UI应该是什么样子。
与其将 "Virtual DOM" 视为一种技术,不如说它是一种模式,人们提到它时经常是要表达不同的东西。
js
💡 注解:这句话很重要!为什么说Virtual DOM是一种"模式"而不是"技术"?
技术通常指具体的实现方案,比如:
- React的Virtual DOM实现
- Vue的Virtual DOM实现
- Preact的Virtual DOM实现
但模式是一种设计思想,可以有多种实现方式:
1. 数据驱动视图的模式:
- 状态 → 虚拟表示 → 真实DOM
- 这个模式不仅限于前端,在游戏开发、GUI开发中都有应用
2. 不同的"虚拟"实现:
- React用JavaScript对象表示
- Flutter用Widget树表示
- 游戏引擎用场景图表示
3. 相同模式,不同表达:
- 有人说Virtual DOM是为了性能优化
- 有人说Virtual DOM是为了开发体验
- 有人说Virtual DOM是为了跨平台渲染
- 这些都对,因为它们都是这个模式的不同价值体现
所以当我们讨论Virtual DOM时,重要的是理解这种"用数据描述UI,然后同步到真实渲染目标"的模式思想。
在 React 的世界里,术语 "Virtual DOM" 通常与 React 元素关联在一起,因为它们都是代表了用户界面的对象。而 React 也使用一个名为 "fibers" 的内部对象来存放组件树的附加信息。上述二者也被认为是 React 中 "Virtual DOM" 实现的一部分。
js
💡 注解:这里提到了两个重要概念,让我们分别理解一下:
1. React元素(React Elements):
// 当你写JSX时
const element = <h1 className="greeting">Hello, world!</h1>;
// 实际上创建的是这样的对象
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
},
key: null,
ref: null
};
这就是React元素,它是Virtual DOM的基本单位。
js
2. Fibers:
React 16引入的新架构,可以理解为"升级版的Virtual DOM节点":
const fiberNode = {
type: 'div', // 元素类型
props: { className: 'container' }, // 属性
child: childFiber, // 第一个子节点
sibling: siblingFiber, // 兄弟节点
return: parentFiber, // 父节点
// Fiber特有的调度信息
effectTag: 'UPDATE', // 更新标记
expirationTime: 1234567890, // 过期时间
alternate: oldFiber, // 上一次的Fiber节点
// 组件状态
memoizedState: componentState, // 缓存的状态
memoizedProps: props, // 缓存的props
}
Fiber让React能够:
- 时间切片:将大任务拆分成小任务,避免阻塞主线程
- 优先级调度:紧急更新可以打断低优先级更新
- 并发渲染:支持Suspense、并发特性等
所以Virtual DOM实际上包含了React元素和Fiber两个层面的实现。
Shadow DOM 和 Virtual DOM 是一回事吗?
不,他们不一样。Shadow DOM 是一种浏览器技术,主要用于在 web 组件中封装变量和 CSS。Virtual DOM 则是一种由 Javascript 类库基于浏览器 API 实现的概念。
js
💡 注解:这两个概念经常被混淆,让我们用代码来看看区别:
Shadow DOM(浏览器原生API):
class MyButton extends HTMLElement {
constructor() {
super();
// 创建Shadow DOM,实现真正的样式和DOM隔离
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
button { color: red; } /* 这个样式被封装,不会影响外部 */
</style>
<button><slot></slot></button>
`;
}
}
customElements.define('my-button', MyButton);
Virtual DOM(JavaScript库概念):
const virtualButton = {
type: 'button',
props: {
style: { color: 'red' },
children: ['点击我']
}
};
// 通过React的渲染引擎转换为真实DOM
ReactDOM.render(virtualButton, container);
简单对比:
特性 | Shadow DOM | Virtual DOM |
---|---|---|
性质 | 浏览器原生API | JavaScript库概念 |
作用 | 样式和DOM封装隔离 | 性能优化和开发体验 |
隔离 | 真实的样式和DOM隔离 | 抽象层,最终还是操作真实DOM |
应用场景 | Web Components | React、Vue等框架 |
html
Shadow DOM解决的是组件样式隔离问题,Virtual DOM解决的是DOM操作性能和开发体验问题。
总结
html
💡 Virtual DOM的核心价值在于:
1. 抽象化:用JavaScript对象描述UI结构
2. 声明式:专注于"要什么"而不是"怎么做"
3. 自动化:框架负责DOM操作的优化
4. 可预测:相同的数据总是产生相同的UI
这种模式让我们能够用更直观的方式构建复杂的用户界面,同时获得更好的性能和开发体验。