深入浅出Vue3 虚拟 DOM

Posted 掘金安东尼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入浅出Vue3 虚拟 DOM相关的知识,希望对你有一定的参考价值。

序言

首发在我的博客 深入 Vue3 虚拟 DOM

译自:diving-into-the-vue-3s-virtual-dom-medium

此篇我们将深入 Vue3 虚拟 DOM,以及了解它是如何遍历找到对应 vnode 的。

多数情况下我们不需要考虑 Vue 组件内部是如何构成的。但有一些库会帮助我们理解,比如 Vue Test Utils 的 findComponent 函数。还有一个我们都应该很熟悉的 Vue 开发工具 —— Vue DevTools,它显示了应用的组件层次结构,并且我们可以对它进行编辑操作等。

我们本篇要做的是:实现 Vue Test Utils API 的一部分,即 findComponent 函数。

设计 findComponent

首先,我们都知道虚拟 DOM 是基于“提升性能”提出的,当数据发生变化时,Vue 会判断此是否需要进行更新、或进行表达式的计算、或进行最终的 DOM 更新。

比如这样:

- div 
- span (show: true)
- 'Visible'

它的内部层次关系是:

htmlDivElement -> HTMLSpanElement -> TextNode

如果 show 属性变成 false。Vue 虚拟 DOM 会进行如下更新:

- div 
- span (show: false)
- 'Visible'

接着,Vue 会更新 DOM,移除'span' 元素。

那么,我们设想一下,findComponent 函数,它的调用可能会是类似这样的结构:

const { createApp } = require('vue')

const App = {
template: `
<C>
<B>
<A />
</B>
</C>
`
}

const app = createApp(App).mount('#app')

const component = findComponent(A, { within: app })

// 我们通过 findComponent 方法找到了 <A/> 标签。

打印 findComponent

接着,我们先写几个简单组件,如下:

// import jsdom-global. We need a global `document` for this to work.
require('jsdom-global')()
const { createApp, h } = require('vue')

// some components
const A = {
name: 'A',
data() {
return { msg: 'msg' }
},
render() {
return h('div', 'A')
}
}

const B = {
name: 'B',
render() {
return h('span', h(A))
}
}

const C = {
name: 'C',
data() {
return { foo: 'bar' }
},
render() {
return h('p', { id: 'a', foo: this.foo }, h(B))
}
}

// mount the app!
const app = createApp(C).mount(document.createElement('div'))
  • 我们需要在 Node.js v14+ 环境,因为我们要用到 可选链。且需要安装 Vue、jsdom 和 jsdom-global。

我们可以看到 A , B , C 三个组件,其中 A , C 组件有 data 属性,它会帮助我们深入研究 VDOM。

你可以打印试试:

console.log(app)
console.log(Object.keys(app))

结果为 {},因为 Object.keys 只会显示可枚举的属性。

我们可以尝试打印隐藏的不可枚举的属性

console.log(app.$)

可以得到大量输出信息:

<ref *1> { 
uid: 0,
vnode: {
__v_isVNode: true,
__v_skip: true,
type: {
name: 'C',
data: [Function: data],
render: [Function: render],
__props: []
}, // hundreds of lines ...

再打印:

console.log(Object.keys(app.$))

输出:

Press ENTER or type command to continue 
[
'uid', 'vnode', 'type', 'parent', 'appContext', 'root', 'next', 'subTree', 'update', 'render', 'proxy', 'withProxy', 'effects', 'provides', 'accessCache', 'renderCache', 'ctx', 'data', 'props', 'attrs', 'slots', 'refs', 'setupState', 'setupContext', 'suspense', 'asyncDep', 'asyncResolved', 'isMounted', 'isUnmounted', 'isDeactivated', 'bc', 'c', 'bm', 'm', 'bu', 'u', 'um', 'bum', 'da', 'a', 'rtg', 'rtc', 'ec', 'emit', 'emitted'
]

我们可以看到一些很熟悉的属性:比如 slotsdatasuspense 是一个新特性,emit 无需多言。还有比如 attrsbc、 cbm 这些是生命周期钩子:bc 是 beforeCreatec 是 created。也有一些内部唯一的生命周期钩子,如 rtg,也就是 renderTriggered, 当 props 或 data 发生变化时,用于更新操作,从而再渲染。

本篇我们需要特别关注的是:vnodesubTreecomponenttype 和 children

匹配 findComponent

来先看 vnode,它有很多属性,我们需要关注的是 type 和 component 这两个。

// 打印 console.log(app.$.vnode.component)

console.log(app.$.vnode.component) 
<ref *1> {
uid: 0,
vnode: {
__v_isVNode: true,
__v_skip: true,
type: {
name: 'C',
data: [Function: data],
render: [Function: render],
__props: []
}, // ... many more things ... } }

type 很有意思!它与我们之前定义的 C 组件一样,我们可以看到它也有 [Function: data](我们在前面定义了一个 msg 数据是我们的查找目标)。实际上我们尝试可以以下打印:

console.log(C === app.$.vnode.component.type) //=> true

天呐!二者竟然是相等的!

以上是关于深入浅出Vue3 虚拟 DOM的主要内容,如果未能解决你的问题,请参考以下文章

Vue面试必问虚拟dom和diff算法,一次解决

vue3.2 基础及常用方法

尤大直播后,再来看Vue3的虚拟dom

尤大直播后24小时,让大圣带你重写Vue3的虚拟dom

Vue3.0 dom diff 算法

vue虚拟dom实现原理