Vue虚拟DOM原理

您所在的位置:网站首页 vue虚拟dom如何渲染成真实dom Vue虚拟DOM原理

Vue虚拟DOM原理

2023-03-30 23:39| 来源: 网络整理| 查看: 265

知识脉络什么是虚拟 DOM虚拟 DOM(Virtual DOM) 是由普工的 JavaScript 对象来描述 DOM对象状态的变化首先作用于虚拟 DOM,最终映射到真实的DOM中Vue.js2.x内部使用的虚拟 DOM是改造的Snabbdom为什么要使用虚拟 DOM虚拟DOM可以跟踪状态变化,并通过比较前后两次状态差异更新真实的DOM虚拟DOM是间接操作DOM,使得开发过程更加关注在业务代码的实现,而不需要关注如何操作DOM,从而提高开发效率虚拟DOM在首次渲染的时候肯定不如直接操作 DOM,因为要维护一层额外的虚拟 DOM,如果后续有频繁操作 DOM 的操作,这个时候才会有性能的提升虚拟DOM的作用维护视图和状态的关系复杂视图情况下提升渲染性能跨平台浏览器平台渲染DOM服务端渲染SSR (Nuxt.js/ Next.js )原生应用 (Weex/ React Native)小程序 (mpvue / uni-app)

虚拟 DOM 在更新真实 DOM 之前会通过 Diff 算法来对比两个新旧两个虚拟 DOM 树之间的差异,实现局部更新,最终把差异更新到真实 DOM虚拟DOM库snabbdomVue.js2.x内部使用的虚拟 DOM是改造的Snabbdom大约200多行代码可以通过模块来拓展功能元am使用TypeScript开发是最快的虚拟DOM之一virtual-dom最早的虚拟DOM开源库虚拟DOM的实现原理snabbdominit函数将返回一个patch函数式,patch函数会将虚拟DOM转换成真实DOM渲染到截面上init函数参数是个数组,里面用来配置

h函数同vue中的函数一样,用来创建虚拟节点有多个参数,我们常用其中的两个参数一:标签+选择器参数二:如果是字符串,那么就代表标签中的文本内容比如创建一个div标签,他的id是container,类是cls,内部文本内容是Hello world,如下let vnode = h('div#container.cls', 'Hello World')

案例一:

import { h, init } from 'snabbdom' ​ // 1. hello world // 参数:数组,模块 // 返回值:patch函数,作用对比两个vnode的差异更新到真实DOM let patch = init([]) ​ // h函数有多个参数,以其中最常用的两个为例 // 第一个参数:标签+选择器 // 第二个参数:如果是字符串的话就是标签中的内容,如果有别的节点可以用数组 let vnode = h('div#container.cls','Hello World') ​ let app = document.querySelector('#app') // patch函数有两个参数 // 第一个参数:旧的VNode,也可以是个DOM元素,内部会把DOM元素转换成VNode // 第二个参数:新的VNode // 返回值:VNode,这个返回的VNode会作为下一次老的VNode let oldVnode = patch(app, vnode) ​ // 假设对htmlwe文件中的#app的div进行更新,用下面的div及内容进行替换 vnode = h('div#container.xxx', 'Hello Snabbdom') ​ patch(oldVnode, vnode) ​

案例二:

// 2. div中放置子元素 h1,p import { h, init } from 'snabbdom' ​ let patch = init([]) ​ let vnode = h('div#container', [ h('h1', 'Hello Snabbdom'), h('p', '这是一个p标签') ]) ​ let app = document.querySelector('#app') // app是选择要渲染到的位置 ​ let oldVnode = patch(app, vnode) // vnode 是当前创建的节点,patch方法时把当前创建的虚拟DOM渲染到app位置处,并返回渲染后的界面并转换成虚拟DOM,渲染后的oldVnode将作为以后的老节点 ​ setTimeout(() => { vnode = h('div#container', [ h('h1', 'Hello World'), h('p', 'Hello P') ]) patch(oldVnode, vnode) ​ // 清空div中的内容 patch(oldVnode, h('!')) // ! 将会渲染成注释标签 }, 2000);

snabbdom模块模块的作用snabbdom 的核心库并不能处理DOM元素的属性/样式/事件等,可以通过注册snabbdom默认提供的模块来实现(类似于插件机制)snabbdom中的模块可以用来扩展snabbdom的功能snabbdom中的模块的实现时通过注册全局钩子函数来实现的,是整个vnode生命周期过程中被触发的函数官方提供的模块attributes - 用来设置DOM的属性,通过setAttributes来实现props - 用来设置DOM属性,通过 . 的形式添加的dataset - 用来处理HTML5中的data- 自定义属性的class - 用来切换类样式,不是定义类样式,定义的话直接在h函数的第一个参数 . 的形式style - 用来设置行内样式,并且很容易设置动画eventlisteners - 用来注册或者移除事件的模块的使用步骤导入需要的模块init() 中注册模块h() 函数的第二个参数处设置成对象的形式,并传入模块中使用的数据import { init, h } from 'snabbdom' // 1. 导入模块 import {styleModule} from 'snabbdom/modules/style' import {eventlistenersModule} from 'snabbdom/modules/eventlisteners' // 2. 注册模块 let patch = init([ styleModule, eventlistenersModule ]) // 3. 使用 h() 函数的第二个参数传入模块需要的数据(对象) let vnode = h('div', [ h('h1', { style: { backgroundColor: 'red'} }, 'Hello Snabbdom'), h('p', { on: {click: eventHandler} }, '这是p标签') ]) ​ function eventHandler () { console.log('点击我了') } ​ let app = document.querySelector('#app') ​ let oldVnode = patch(app, vnode) ​ ​ vnode = h('div', 'hello') patch(oldVnode, vnode) ​

snabbdom源码概述如何学习源码先宏观了解带着目标看源码看源码的过程要不求甚解调试参考资料源码地址:https://github.com/snabbdom/snabbdomsnabbdom的核心init() 设置模块,创建patch函数使用h() 函数创建JavaScript对象(VNode) 来描述真实的DOMpatch() 比较新旧两个VNode把变化的内容更新到真实的DOM树

Snabbdom 源码实现h函数在使用 Vue 的时候的 h() 函数和snabbdom中的h函数功能一样都是用来穿件VNode对象,但是Vue中增强了 h 函数,实现了组件的机制new Vue({ router, store, render: h => h(App) }).$mount('#app') h() 函数最早见于 hyperscript,使用 JavaScript 创建超文本,也就是 html 字符串Snabbdom 中的 h() 函数源于 hyperscript,但是不是用来创建超文本,而是创建 VNode函数重载概念参数个数或类型不同的函数JavaScript 中没有重载的概念,同名函数会被后者覆盖TypeScript 中有重载,不过重载的实现还是通过代码调整参数

重载示例// 参数个数不同 function add (a: number, b: number) { console.log(a + b) } function add (a: number, b: number, c: number) { console.log(a + b + c) } add(1, 2) // 调用第一个add add(1, 2, 3) // 调用第二个add ​ ​ // 参数类型不同 function add (a: number, b: number) { console.log(a + b) } function add (a: number, b: string) { console.log(a + b + c) } add(1, 2) // 调用第一个add add(1, "2") // 调用第二个add h函数实现源码位置src/h.ts// h 函数的重载 export function h(sel: string): VNode; export function h(sel: string, data: VNodeData): VNode; export function h(sel: string, children: VNodeChildren): VNode; export function h(sel: string, data: VNodeData, children: VNodeChildren): VNode; export function h(sel: any, b?: any, c?: any): VNode { var data: VNodeData = {}, children: any, text: any, i: number; // 处理参数,实现重载的机制 if (c !== undefined) { // 处理三个参数的情况 // sel、data、children/text data = b; // 如果 c 是数组 if (is.array(c)) { children = c; } // 如果 c 是字符串或者数字 else if (is.primitive(c)) { text = c; } // 如果 c 是 VNode else if (c && c.sel) { children = [c]; } } else if (b !== undefined) { // 处理两个参数的情况 // 如果 b 是数组 if (is.array(b)) { children = b; } // 如果 b 是字符串或者数字 else if (is.primitive(b)) { text = b; } // 如果 b 是 VNode else if (b && b.sel) { children = [b]; } else { data = b; } } if (children !== undefined) { // 处理 children 中的原始值(string/number) for (i = 0; i

这种遍历方式最终会有两种情况:

当老节点的所有子节点先遍历完 (oldStartIdx > oldEndIdx),循环结束新节点的所有子节点先遍历完 (newStartIdx > newEndIdx),循环结束如果老节点的数组先遍历完(新节点个数 > 老节点个数),说明新节点有剩余,把剩余节点批量插入到右边

2.如果新节点的数组先遍历完(老节点个数> 新节点个数),说明老节点有剩余,把剩余节点批量删除

源码实现function updateChildren(parentElm: Node, oldCh: Array, newCh: Array, insertedVnodeQueue: VNodeQueue) { let oldStartIdx = 0, newStartIdx = 0; let oldEndIdx = oldCh.length - 1; let oldStartVnode = oldCh[0]; let oldEndVnode = oldCh[oldEndIdx]; let newEndIdx = newCh.length - 1; let newStartVnode = newCh[0]; let newEndVnode = newCh[newEndIdx]; let oldKeyToIdx: any; let idxInOld: number; let elmToMove: VNode; let before: any; ​ // 同级别节点比较 while (oldStartIdx 这种成因是:当数据变化后会执行updateChildren对比新旧节点子节点之间的差异对比子节点差异时会判断子节点的key是否相同由于当前没有设置key,对比新旧子节点时由于新旧Vnode对象相同,会重用,只会更新内部变化的文字所以插入后的100项目,它的checkbox和span都是重用之前1的DOM,只是更新了其中的文本所以导致了这种渲染错误

let patch = init ([attributesModule,eventListenersModule]) ​ const data = [1,2,3,4] let oldVnode = null; ​ function view (data) { let arr =[] data.forEach(item => { // 不设置key arr.push(h('li',[h('input',{ attrs: {type: 'checkbox'} }), h('span', item)])) // 设置key arr.push(h('li',{key: item }[h('input',{ attrs: {type: 'checkbox'} }), h('span', item)])) }) let vnode = h('div', [h('button',{on: { click: function () { data.unshift(100) vnode = view(data) oldVnode = patch(oldVnode, vnode) }}}, '按钮'), h('ul', arr)]) return vnode }

学习总结及笔记目录入口:



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3