众所周知,react、vue等前端框架都是通过操作虚拟dom,减少直接操作真实dom的频率,以提升性能。
因此需要先理解什么是虚拟dom是如何渲染成真实dom,这里使用渐进式的方式去理解如何使用vdom渲染dom。
直接操作DOM的烦恼
假设我们要在网页上显示一个带文字的<div id="app">,用原生JavaScript需要这样写:
使用原生js的方式去创建div节点,创建text节点,然后挂载到根节点
const dom = document.createElement('div')
dom.id = 'app'
document.getElementById('root').append(dom)
const textNode = document.createTextNode('')
textNode.nodeValue = 'hello world'
dom.append(textNode)
第一步:虚拟DOM初体验
虚拟DOM就像一份图纸,我们先描述想要的结构:
// 文字部分的图纸
const textEl = {
type: 'TEXT_ELEMENT',
props: {
nodeValue: 'hello world',
children: [] // 没有子元素
}
}
// div容器的图纸
const el = {
type: 'div',
props: {
id: 'app',
children: [textEl] // 包含文字节点
}
}
然后按图施工:
const dom = document.createElement(el.type)
dom.id = el.props.id
document.getElementById('root').append(dom)
const textNode = document.createTextNode('')
textNode.nodeValue = textEl.props.nodeValue
dom.append(textNode)
虽然还是要手动操作DOM,但已经能看出 虚拟DOM 与 真实DOM 的对应关系。
第二步:动态生成图纸
前面的图纸是写死的,我们可以创建图纸生成器:
// 文字图纸生成器
const createTextEl = (text) => ({
type: 'TEXT_ELEMENT',
props: {
nodeValue: text,
children: []
}
})
// 通用图纸生成器
const createElement = (type, props, ...children) => ({
type,
props: {
...props,
children
}
})
// 生成一套图纸
const el = createElement('div', {id: 'app'})
const textEl = createTextEl('hello world')
// 然后按图施工:
const dom = document.createElement(el.type)
dom.id = el.props.id
const textNode = document.createTextNode('')
textNode.nodeValue = textEl.props.nodeValue
document.getElementById('root').append(dom)
dom.append(textNode)
现在可以像搭积木一样组合元素,生成任意复杂的结构。
第三步:自动化施工队
最后我们创建自动施工函数:
function render(vdom, container) {
// 1. 创建基础建材
const dom = vdom.type === 'TEXT_ELEMENT'
? document.createTextNode('')
: document.createElement(vdom.type)
// 2. 添加细节(处理属性)
Object.keys(vdom.props).forEach(key => {
if(key !== 'children') dom[key] = vdom.props[key]
})
// 3. 递归处理房间(深度优先施工)
vdom.props.children.forEach(child => {
render(child, dom)
})
// 4. 交房入住
container.append(dom)
}
// 使用示例
const textEl = createTextEl('hello world')
const app = createElement('div', {id: 'app'}, textEl)
render(app, document.getElementById('root'))
这个过程就像:
- 先打好地基(创建基础节点)
- 刷墙铺地板(设置属性)
- 逐个装修每个房间(递归处理子节点)
- 整栋楼交付使用(插入容器)
为什么需要虚拟DOM?
- 性能优化:批量处理DOM操作,减少直接操作真实DOM的次数
- 跨平台能力:同一套虚拟DOM可以渲染到网页/手机/小程序
- 开发体验:用声明式语法描述界面,更接近自然思维