Vue3中虚拟dom转成真实dom的过程详解
作者:zykk
前言
Vue.js 在其运行过程中会将模板编译成虚拟 DOM (VNode),然后再将 VNode 渲染成实际的 DOM 节点。这个过程是由 Vue 内部的编译器和渲染系统完成的.
虽然Vue 3 的虚拟 DOM 编译过程对于开发者来说通常是透明的,但了解这些内部机制有助于更好地理解和优化应用程序。
如果你对 Vue 3 的内部实现感兴趣,可以查阅 Vue 3 的官方文档或阅读 Vue 3 的源码来深入了解这一过程。
Vue 3 中虚拟 DOM 的编译过程
1. 模板编译
在 Vue 3 中,模板编译主要由两个阶段组成:解析和优化。
- 解析阶段:Vue 3 的编译器会将模板字符串解析成一个抽象语法树 (Abstract Syntax Tree, AST),这个树结构表示了模板的结构和内容。编译器会识别出模板中的各种指令(如
v-if
,v-for
,v-bind
等)并将它们转换成对应的 AST 节点。 - 优化阶段:编译器会对 AST 进行优化,以减少不必要的计算和 DOM 操作。例如,它可以提前计算静态节点,并将其标记为静态的,这样在渲染时就不需要重新生成这些节点。
2. 生成渲染函数
一旦 AST 被创建并优化后,编译器会生成一个渲染函数,这个函数可以用来创建虚拟 DOM 节点(VNode)。渲染函数通常会利用 Vue 内置的 h
函数(createVNode
的别名)来创建 VNode。
3. 创建虚拟 DOM (VNode)
在 Vue 3 中,h
函数被用来创建 VNode。一个 VNode 是一个 JavaScript 对象,它包含了关于 DOM 节点的信息,如标签名、属性、子节点等。例如:
const vnode = h( 'div', // 标签名 { id: 'app' }, // 属性对象 'Hello Vue 3!' // 子节点 );
4. 渲染到真实 DOM
当 VNode 被创建后,Vue 会使用高效的算法来比较新旧 VNode,并更新真实的 DOM。这个过程称为 patching。Vue 3 的 diff 算法旨在最小化 DOM 操作,从而提高性能。
今天来简单介绍一下如何将一份虚拟dom转成真实dom。
vdomToDom
虚拟dom结构已有,挂载到root节点上,请问如何实现render函数?
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="root"></div> <script> const vnode = { tag: 'div', attrs: { id: 'app', class:'box' }, children: [ { tag: 'span', children: [{ tag: 'a', children: [], }], }, { tag: 'span', children: [{ tag: 'a', children: [], }] } ] } render(vnode,document.getElementById('root')) function render(vnode, container) { } </script> </body> </html>
我们先来看一眼这份虚拟dom长什么样。
首先最外层有个id为app类名为box的div,里面有两个子节点span,第一个子节点中又有一个a,第二个子节点中也有一个a
那么vue中编译dom
的原理是什么,我们来一份简易版看看。
首先我们就想有一个方法只要给一个虚拟dom就能生成dom,然后将其挂载到root上去,接下来就是如何实现createDom
function render(vnode, container) { const newDom = createDom(vnode) container.appendChild(newDom) }
function createDom(vnode) { const { tag, attrs, children } = vnode const dom = document.createElement(tag) if (typeof attrs === 'object' && attrs !== null) { updateProps(dom, {}, attrs) // 为dom添加属性 } if (children.length > 0) { reconcileChildren(children, dom) // 为dom添加子容器 } return dom }
然后思考,如何为子容器添加属性以及如何为容器添加子容器?
function updateProps(dom, oldProps = {}, newProps = {}) { for (const key in newProps) { if (key === 'style') { let styleObj = newProps[key] for (let attr in styleObj) { dom.style[attr] = styleObj[attr] } } else { // id / class dom[key] = newProps[key] } } } function reconcileChildren(children, dom) { for (let child of children) { render(child, dom) } }
完整代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="root"></div> <script> const vnode = { tag: 'div', attrs: { id: 'app', className: 'box' }, children: [ { tag: 'span', children: [{ tag: 'a', children: [], }], }, { tag: 'span', children: [{ tag: 'a', children: [], }] } ] } render(vnode, document.getElementById('root')) function render(vnode, container) { const newDom = createDom(vnode) container.appendChild(newDom) } function createDom(vnode) { const { tag, attrs, children } = vnode const dom = document.createElement(tag) if (typeof attrs === 'object' && attrs !== null) { updateProps(dom, {}, attrs) // 为dom添加属性 } if (children.length > 0) { reconcileChildren(children, dom) // 为dom添加子容器 } return dom } function updateProps(dom, oldProps = {}, newProps = {}) { for (const key in newProps) { if (key === 'style') { let styleObj = newProps[key] for (let attr in styleObj) { dom.style[attr] = styleObj[attr] } } else { // id / class dom[key] = newProps[key] } } } function reconcileChildren(children, dom) { for (let child of children) { render(child, dom) } } </script> </body> </html>
效果
以上就是Vue3中虚拟dom转成真实dom的过程详解的详细内容,更多关于Vue3 虚拟dom转成真实dom的资料请关注脚本之家其它相关文章!