Vue3学习笔记--虚拟DOM

304 阅读5分钟

什么是虚拟DOM?

虚拟DOM(Virtual DOM)是一种用于提高Web应用性能的编程概念,通常在前端框架中使用,例如React和Vue。 目的就是将实际的DOM结构虚拟的表示出来(在vue中利用纯js对象来表示一个虚拟节点),保存在内存中,然后跟真实的DOM保持同步,以下是对虚拟DOM的基本解释:

  1. 实际DOM(Document Object Model): 在Web开发中,浏览器将网页呈现为一个DOM树,它表示文档的结构和内容。实际DOM是这个由浏览器维护的树状结构。

  2. 虚拟DOM: 虚拟DOM是一个存在于内存中的、轻量级的、对实际DOM的抽象表示。它是由JavaScript对象构成的树,与实际DOM相似,但更简化。框架会使用虚拟DOM来追踪应用状态的变化。

  3. 工作原理:

    • 状态变化触发: 当应用的状态发生变化时,框架会创建一个新的虚拟DOM树,代表新的状态。
    • 虚拟DOM比较: 框架会将新旧虚拟DOM树进行比较,找出两者之间的差异,即需要进行更新的部分。
    • 差异计算: 通过比较算法,确定最小的变化集,以提高性能。
    • 实际DOM更新: 只有被确定需要更新的部分才会被转换为实际DOM的操作,从而尽可能减少对实际DOM的直接操作。
    • 性能提升: 通过减少实际DOM的操作次数,以及优化更新的范围,虚拟DOM可以提高应用的性能和响应速度。
  4. 优势:

    • 性能优化: 通过最小化实际DOM操作,减少渲染引擎的负担,提高性能。
    • 简化复杂性: 使用虚拟DOM可以使得状态管理和UI更新更为简洁,因为开发者无需直接处理实际DOM的复杂细节。

vue模板的渲染方式

  1. 模板解析: 编译过程开始于模板解析,其中包括将HTML模板字符串转换为AST(抽象语法树)表示。AST 是一个树状结构,描述了模板的结构,包括元素、属性、文本节点等。
  2. 静态分析: 模板编译器会进行静态分析,以确定哪些部分是静态的(不会改变的)和动态的(可能会改变的)。静态的部分可以在编译时进行一次性的处理,而不需要在每次渲染时都重新计算。
  3. 优化: 对静态节点和静态属性进行优化,例如生成静态节点的渲染函数,避免重复计算。
  4. 生成渲染函数: 编译器将AST转换为运行时会被编译为渲染函数(render函数),render函数用于创建虚拟 DOM,并最终渲染到实际的 DOM。
  5. 挂载: 运行时渲染器调用渲染函数,遍历返回的虚拟 DOM 树,并基于它创建实际的 DOM 节点。
  6. 响应式代码: 如果模板中包含了响应式数据绑定,编译器会生成代码,建立与数据的响应式关系,以确保当数据变化时,视图能够及时更新。
  7. 最终生成: 编译器将所有的优化、生成的代码以及模板相关的信息组装成一个可执行的渲染函数。这个渲染函数可以在运行时被调用,用于创建虚拟DOM并最终渲染到实际的DOM。
  8. 更新:当一个依赖发生变化后,副作用会重新运行,这时候会创建一个更新后的虚拟 DOM 树。运行时渲染器遍历这棵新树,将它与旧树进行比较,然后将必要的更新应用到真实 DOM 上去。

官方图解

image.png

vue中手动创建虚拟DOM的方式

通过引入h函数或者createVNode来创建虚拟dom

<script setup lang="ts">
import { h, render, type VNode,createVNode } from 'vue'
//函数编程
const myDiv = () => {
  //h函数返回的是一个虚拟DOM
  const element:VNode = h('h1', { id: '666' }, '我是通过h函数创建的虚拟dom')
  // console.log(element)
  return element
}
const vnode = myDiv()
const mount:HTMLElement = document.getElementById('app') as HTMLElement
//虚拟DOM最终会被渲染器调用render函数,来创建为真实的DOM元素
const a= render(vnode,mount)
console.log(a)
</script>
  • h函数是vue暴露给开发者来手动创建虚拟DOM的,但其实内部源码页在调用ceratedVnode来创建-具体用法和参数可查看官网 h函数部分源码
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
  const l = arguments.length
  if (l === 2) {
    if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
      // single vnode without props
      if (isVNode(propsOrChildren)) {
        return createVNode(type, null, [propsOrChildren])
      }
      // props without children
      return createVNode(type, propsOrChildren)
    } else {
      // omit props
      return createVNode(type, null, propsOrChildren)
    }
  } else {
    if (l > 3) {
      children = Array.prototype.slice.call(arguments, 2)
    } else if (l === 3 && isVNode(children)) {
      children = [children]
    }
    return createVNode(type, propsOrChildren, children)
  }
}
  • 不管你用h还是createVNode创建的虚拟DOM最终都需要去调用render函数去执行渲染为真实的DOM
  • 当组合式 API 与模板一起使用时,setup() 钩子的返回值是用于暴露数据给模板。然而当我们使用渲染函数时,可以直接把渲染函数返回:
<template>
  <div>
  //调用返回到虚拟dom
    <my-div></my-div>
  </div>
</template>
<!-- 组合式API写法:直接返回h函数,交给模板去渲染 -->
<!-- 也可以不通过模板,手动调用render函数去挂载和渲染 -->
<script setup lang="ts">
import { h, } from 'vue'
//<!-- 组合式API写法:直接返回h函数,交给模板去渲染 -->
const myDiv = () => {
  //h函数返回的是一个虚拟DOM
  return h('h1', { id: '666' }, '我是通过h函数创建的虚拟dom')
}
</script>
  • 也可以不通过模板,通过render函数去渲染
<script setup lang="ts">
import { h, render, } from 'vue'
const myDiv = () => {
  //h函数返回的是一个虚拟DOM
  return h('h1', { id: '666' }, '我是通过h函数创建的虚拟dom')
}
//也可以不通过模板,手动调用render函数去挂载和渲染
const vnode = myDiv()
const mount:HTMLElement = document.getElementById('app') as HTMLElement
//虚拟DOM最终会被渲染器调用render函数,来创建为真实的DOM元素
render(vnode,mount)
</script>
  • 选项式也类似,总之要么通过render去挂挂载或者返回给template,最终编译渲染
  • 注:选项式API,模板会覆盖render函数
export default{
    data(){
      return{
        msg:'组合式API写法:直接返回h函数,交给模板去渲染'
      }
    },
  render(){
    return h('div',{id:'852585'},this.msg)
  }
}
  • 组合式(非setup语法糖下),在setup函数中返回h(),会覆盖模板(假如存在模板)
setup(){
    return ()=>h('div',{id:'852585'},'852')
  },