Vue 渲染过程
Vue 从模板到 DOM 的完整渲染流程
问题
Vue 组件从模板到最终渲染成 DOM 经历了哪些步骤?
解答
渲染流程概览
模板 template
↓ 编译
渲染函数 render function
↓ 执行
虚拟 DOM (VNode)
↓ patch
真实 DOM
1. 模板编译
Vue 将模板编译成渲染函数,分为三个阶段:
// 模板
const template = `<div id="app">{{ message }}</div>`
// 1. parse: 模板 -> AST
const ast = parse(template)
// 结果:{ tag: 'div', attrs: [{name: 'id', value: 'app'}], children: [...] }
// 2. optimize: 标记静态节点(优化 diff)
optimize(ast)
// 3. generate: AST -> 渲染函数代码
const code = generate(ast)
// 结果:'with(this){return _c("div",{attrs:{"id":"app"}},[_v(_s(message))])}'
2. 渲染函数执行
// Vue 内部的渲染函数辅助方法
// _c = createElement 创建元素 VNode
// _v = createTextVNode 创建文本 VNode
// _s = toString 转字符串
// 渲染函数执行后返回 VNode 树
const vnode = render.call(vm)
// VNode 结构
const vnode = {
tag: 'div',
data: { attrs: { id: 'app' } },
children: [
{ text: 'Hello Vue' }
],
elm: undefined // 对应的真实 DOM,初始为空
}
3. Patch 过程(VNode -> DOM)
// 首次渲染:创建真实 DOM
function createElm(vnode) {
const el = document.createElement(vnode.tag)
// 设置属性
if (vnode.data && vnode.data.attrs) {
for (const key in vnode.data.attrs) {
el.setAttribute(key, vnode.data.attrs[key])
}
}
// 递归创建子节点
if (vnode.children) {
vnode.children.forEach(child => {
if (child.text) {
el.appendChild(document.createTextNode(child.text))
} else {
el.appendChild(createElm(child))
}
})
}
vnode.elm = el // 保存真实 DOM 引用
return el
}
4. 更新渲染(Diff + Patch)
// 数据变化时触发更新
vm.message = 'Updated'
// 1. 重新执行渲染函数,生成新 VNode
const newVnode = render.call(vm)
// 2. diff 比较新旧 VNode
function patch(oldVnode, newVnode) {
// 同一节点,比较更新
if (sameVnode(oldVnode, newVnode)) {
patchVnode(oldVnode, newVnode)
} else {
// 不同节点,替换
const parent = oldVnode.elm.parentNode
const newElm = createElm(newVnode)
parent.replaceChild(newElm, oldVnode.elm)
}
}
// 判断是否同一节点
function sameVnode(a, b) {
return a.key === b.key && a.tag === b.tag
}
// 更新节点
function patchVnode(oldVnode, newVnode) {
const el = newVnode.elm = oldVnode.elm
// 更新属性
updateAttrs(el, oldVnode.data, newVnode.data)
// 更新子节点(核心 diff 算法)
updateChildren(el, oldVnode.children, newVnode.children)
}
5. 完整生命周期中的渲染
new Vue({
template: '<div>{{ msg }}</div>',
data: { msg: 'Hello' }
})
// 执行顺序:
// 1. beforeCreate
// 2. 初始化响应式数据
// 3. created
// 4. 编译模板(如果有 template)
// 5. beforeMount
// 6. 执行 render -> 生成 VNode -> patch 到 DOM
// 7. mounted(此时 $el 可用)
// 数据更新时:
// 1. beforeUpdate
// 2. 重新 render -> diff -> patch
// 3. updated
关键点
- 编译阶段:template → AST → optimize → render function
- 渲染阶段:执行 render 函数生成 VNode 树
- 挂载阶段:patch 将 VNode 转为真实 DOM
- 更新阶段:数据变化 → 重新 render → diff 比较 → 最小化 DOM 操作
- 优化策略:静态节点标记跳过 diff,key 帮助复用节点
目录