vue 虚拟dom_深入Vue 3s虚拟dom

news/2024/7/10 0:40:23 标签: vue

vue 虚拟dom

In this article, we do a dive into the virtual dom in Vue.js 3, and how we can traverse it with the goal of finding a specific component (or what we will call a vnode - more on this soon).

在本文中,我们将深入研究Vue.js 3中的虚拟dom ,以及如何遍历虚拟dom ,以期找到一个特定的组件(或称为vnode -稍后将对此进行更多介绍)。

Most of the time, you don’t need to think about how Vue internally represents your components. Some libraries do make sure of this though — one such library is Vue Test Utils and it’s findComponent function. Another such use case is the Vue DevTools, which show you the component hierarchy for your application, seen on the left hand side of this screenshot.

大多数时候,您无需考虑Vue在内部如何表示您的组件。 但是某些库确实确保了这一点– Vue Test Utils是其中一个库,它是findComponent函数。 另一个这样的用例是Vue DevTools,它显示了您应用程序的组件层次结构,如此屏幕截图的左侧所示。

Image for post

Note: this is a technical article. While I will do my best to explain how everything works, the only real way to fully grasp this is to write your own code and console.log a lot, to see what's really happening. This is often the nature of the type of recursive algorithm we will be writing.

注意:这是一篇技术文章。 尽管我将尽最大努力解释一切工作原理,但要完全掌握这一点的唯一真正方法是编写大量自己的代码和console.log ,以查看实际情况。 这通常是我们将要编写的递归算法类型的本质。

An alternative would be to watch the accompanying screencast, will I will make free indefinitely. You can find the source code for this example here.

另一种选择是观看随附的截屏视频 ,我将无限期免费提供。 您可以在此处找到此示例的源代码 。

vuejs-course.com/">
Image for post
Interested in advanced content for Vue.js 3? Check out my course and weekly screencasts on https://vuejs-course.com.
对Vue.js 3的高级内容感兴趣吗? 在 https://vuejs-course.com上查看我的课程和每周的屏幕录像。

虚拟DOM (The Virtual DOM)

For various reasons, one of which is performance, Vue keeps an internal representation of the component hierarchy. This is called a Virtual DOM (VDOM). When something changes (for example a prop) Vue will figure out if something needs to be updated, calculate the new representation, and finally, update the DOM. A trivial example might be:

由于各种原因,其中之一就是性能,Vue保留了组件层次结构的内部表示。 这称为虚拟DOM(VDOM)。 当某些内容发生变化(例如道具)时,Vue会确定是否需要更新某些内容,计算新的表示形式,最后更新DOM。 一个简单的例子可能是:

It could be represented like this:

可以这样表示:

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

So it would be HTMLDivElement -> HTMLSpanElement -> TextNode. If show becomes false, Vue would update it’s Virtual DOM:

因此它将是HTMLDivElement -> HTMLSpanElement -> TextNode 。 如果show变成false ,Vue将更新它的虚拟DOM:

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

Then, finally, Vue would update the DOM, removing the <span>.

然后,最后,Vue将更新DOM,删除<span>

我们的目标— findComponent (Our Goal — findComponent)

Our goal will be to implement a subset of findComponent, part of the Vue Test Utils API. We will write something like this:

我们的目标是实现findComponent的子集,它是Vue Test Utils API的一部分。 我们将这样写:

To have a working findComponent function, we need to traverse the Virtual DOM, a tree like structure of arbitrary depth. Let's get started.

要拥有一个有效的findComponent函数,我们需要遍历Virtual DOM,这是一个任意深度的树状结构。 让我们开始吧。

检查组件内部 (Inspecting the Component Internals)

If you would like to follow along, you can grab the source code here. We will just use Node.js (v14, so we can use the ? or "optional chaining" operator). You will need Vue, jsdom and jsdom-global installed.

如果您想继续学习,可以在此处获取源代码。 我们将仅使用Node.js(v14,因此我们可以使用?或“可选链接”运算符)。 您将需要安装Vue,jsdom和jsdom-global。

Start with setting up a simple app with some components:

首先设置具有一些组件的简单应用程序:

Some of the components have data and props - this will be useful as we investigate the Virtual DOM Vue creates for our app.

其中一些组件具有dataprops -当我们调查为应用程序创建的Virtual DOM Vue时,这将很有用。

If you go ahead and do either console.log(app) or console.log(Object.keys(app)), you don't see anything - just {}. Object.keys will only show enumerable properties - ones that show up in a for...of loop. There are some hidden non enumerable properties, though, which we can console.log. Try doing console.log(app.$). You get a whole bunch of information:

如果继续执行console.log(app)console.log(Object.keys(app)) ,则看不到任何内容-仅{}Object.keys将仅显示可枚举的属性-出现在for...of循环中的属性。 但是,有一些隐藏的不可枚举的属性,我们可以使用console.log 。 尝试做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 ...

You can do console.log(Object.keys(app.$)) to have a summary of what's available:

您可以执行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'
]

It’s obvious what some of the properties do — slots and data for example. suspense is used for the new <Suspense> feature. emit is something every Vue dev knows, same as attrs. bc, c, bm etc are lifecycle hooks - bc is beforeCreate, c is created. There are some internal only lifecycle hooks, like rtg - it's renderTriggered, used for updates after something changes and causes a re-render, like props or data changing.

显而易见,某些属性的作用-例如slotsdatasuspense用于新的<Suspense>功能。 emit是每个Vue开发人员都知道的东西,与attrsbccbm等是生命周期钩- bcbeforeCreateccreated 。 有一些内部唯一的生命周期挂钩,例如rtg它是renderTriggered ,用于在发生某些更改并导致重新渲染(例如propsdata更改)后进行更新。

We are interested in vnode, subTree, component, type and children.

我们对vnodesubTreecomponenttypechildren感兴趣。

比较组件 (Comparing Components)

Let’s take a look at vnode. Again it has many properties, the two we will look at are type and component:

让我们看一下vnode 。 同样,它具有许多属性,我们将要研究的两个是typecomponent

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 is of interest! It matches the C component we defined earlier. You can see it has a data function (we defined one with a msg variable). In fact, if we compare this to C:

type很有趣! 它与我们前面定义的C组件匹配。 您可以看到它具有data功能(我们使用msg变量定义了一个功能)。 实际上,如果我们将此与C进行比较:

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

It matches! We can also do the same comparison with vnode.type: console.log(C === app.$.vnode.type) //=> true. I am not entirely clear on why there is two properties pointing to the same object. I am still learning. Anyway, we identified a way to match components.

它匹配! 我们还可以使用vnode.type进行相同的比较: console.log(C === app.$.vnode.type) //=> true 。 我不清楚为什么有两个属性指向同一个对象。 我还在学习。 无论如何,我们找到了一种匹配组件的方法。

深入研究虚拟DOM (Diving Deeper into the Virtual DOM)

After a little trial and error, you can eventually find A like this:

经过一番尝试和错误之后,您最终可以找到这样的A

console.log( 
app.$
.subTree.children[0].component
.subTree.children[0].component.type === A) //=> true

In this case, the <div> node would have a subTree.children array with a length of 2.

在这种情况下, <div>节点将具有一个subTree.children数组,其长度为2。

Now we know the recursive nature of the Virtual DOM, we can write a recursive solution! It goes subTree -> children -> component and so on.

现在我们知道了虚拟DOM的递归性质,我们可以编写一个递归解决方案! 它进入subTree -> children -> component等。

编写findComponent (Writing findComponent)

I am using Node.js v14, which supports optional chaining: subTree?.children for example. Before we write a recursive find function, we need some way to know if we have found the component: matches:

我正在使用Node.js v14,它支持可选链接:例如subTree?.children 。 在编写递归查找函数之前,我们需要某种方式来知道是否找到了该组件: matches

function matches(vnode, target) { 
if (!vnode) {
return false
}
return vnode.type === target
}

You could write vnode?.type === target but I like the verbose one a little more.

您可以编写vnode?.type === target但我更喜欢冗长的代码。

We will write two functions. findComponent, which will be the public API that users call, and find, an internal, recursive function.

我们将编写两个函数。 findComponent ,它将是用户调用的公共API并find内部的递归函数。

Let’s start with the public API:

让我们从公共API开始:

function findComponent(comp, { within }) { 
const result = find([within.$], comp) if (result) {
return result
}
}

The third argument is an empty array — because are writing a recursive function, we need some place to keep the components we have found that match the target. We will store them in this array, passing it to each recursive call of find. This way we avoid mutating an array - I find less mutation leads to less bugs (your mileage may vary).

第三个参数是一个空数组-因为要编写一个递归函数,我们需要一些地方来保持我们发现的与目标匹配的组件。 我们将它们存储在此数组中,并将其传递给find每个递归调用。 这样,我们避免了对数组进行变异-我发现较少的变异导致较少的错误(您的里程可能有所不同)。

递归查找 (Recursive find)

When writing a recursive function, you need to have some way to exit, or you will get stuck in an endless loop. Let’s start with that:

在编写递归函数时,您需要某种退出方法,否则您将陷入无限循环。 让我们开始:

function find(vnodes, target) { 
if (!Array.isArray(vnodes)) {
return
}
}

If we have recursed all the way to the bottom of the Virtual DOM (and checked all vnodes in the process) we just return. This will ensure we do not get stuck in a loop. If we run this now:

如果我们一直重复到虚拟DOM的底部(并检查了进程中的所有vnode),我们将返回。 这将确保我们不会陷入循环。 如果我们现在运行此命令:

const result = findComponent(A, { within: app }) //=> undefined

While traversing the vnodes, if we find a matching component, we will just return it. If we did not find a matching component, we may need to dive deeper by checking if vnode.subTree.children is defined. Finally, if it's not, we return the accumulator.

在遍历vnode时,如果找到匹配的组件,则将其返回。 如果找不到匹配的组件,则可能需要检查vnode.subTree.children是否已定义,从而更深入地研究。 最后,如果不是,我们返回累加器。

function find(vnodes, target) {
if (!Array.isArray(vnodes)) {
return
}
return vnodes.reduce((acc, vnode) => {
if (matches(vnode, target)) {
return vnode
}
if (vnode?.subTree?.children) {
return find(vnode.subTree.children, target)
}
return acc
}, {})
}

If you do a console.log inside of the if (vnode?.subTree?.children) { block, you will see we are now at the B component subTree. Remember, the path to A is as follows:

如果在if (vnode?.subTree?.children) {块内执行console.log ,您将看到我们现在位于B组件子树中。 请记住,通往A的路径如下:

app.$ 
.subTree.children[0].component
.subTree.children[0].component.type === A) //=> true

By calling find again: find(vnode.subTree.children, target), the first argument to find on the next iteration will be app.$.subTree.children, which is an array of vnodes. That means we need to do component.subTree.children on the next iteration of find - but we are only checking vnode.subTree.children. We need a check for vnode.component.subTree as well:

通过再次调用findfind(vnode.subTree.children, target) ,在下一次迭代中find的第一个参数将是app.$.subTree.children ,它是vnodes的数组。 这意味着我们需要在find的下一个迭代中执行component.subTree.children但我们仅检查vnode.subTree.children 。 我们还需要检查vnode.component.subTree

function find(vnodes, target) {
if (!Array.isArray(vnodes)) {
return
}
return vnodes.reduce((acc, vnode) => {
if (matches(vnode, target)) {
return vnode
}
if (vnode?.subTree?.children) {
return find(vnode.subTree.children, target)
}
if (vnode?.component?.subTree) {
return find(vnode.component.subTree.children, target)
}
return acc
}, {})
}

And somewhat surprisingly, that’s it. const result = findComponent(A, { within: app }) now returns a reference to A. You can see it working like this:

有点令人惊讶的是const result = findComponent(A, { within: app })现在返回对A的引用。 您可以看到它像这样工作:

console.log( result.component.proxy.msg ) // => 'msg'

If you have used Vue Test Utils before, you may recognise this in a slightly different syntax: wrapper.vm.msg, which is actually accessing the proxy internally (for Vue 3) and the vm for Vue 2. If you are using TypeScript, you may notice proxy does not show up as a valid type - that's because it is internal, not generally intended for use in regular applications, although some internal tools still use it, like Vue Test Utils and Vue DevTools.

如果您以前使用过Vue Test Utils,则可能会以稍微不同的语法来识别这一点: wrapper.vm.msg ,它实际上是在内部访问proxy (对于Vue 3)和vm用于Vue2。如果使用TypeScript,您可能会注意到proxy未显示为有效类型-这是因为它是内部类型,通常不打算在常规应用程序中使用,尽管某些内部工具仍在使用它,例如Vue Test Utils和Vue DevTools。

一个更完整的例子 (A More Complete Example)

This implementation is far from perfect — there are more checks that need to be implemented. For example, this does not work with components using template instead of render, or <Suspense>. A more complete implementation can be found here in the Vue Test Utils source code.

此实现远非完美-需要执行更多检查。 例如,这不适用于使用template而不是render<Suspense> 。 在Vue Test Utils源代码中可以找到更完整的实现。

At the time of this article, the implementation there mutates an array instead of passing a new copy to each recursive call. You can see it here — the functions you want to look at are findAllVNodes and aggregateChildren.

在撰写本文时,此处的实现是对数组进行突变,而不是将新副本传递给每个递归调用。 你可以看到它在这里 -你想看看功能中的是findAllVNodesaggregateChildren

You can find the source code for this example here.

您可以在此处找到此示例的源代码 。

Originally published at https://vuejs-course.com.

最初发布在 https://vuejs-course.com

翻译自: https://medium.com/js-dojo/diving-into-the-vue-3s-virtual-dom-a6b4744032ec

vue 虚拟dom


http://www.niftyadmin.cn/n/1468041.html

相关文章

java递归解析xml_java-web-dom4j解析XML-递归方式

曾哥作者>东方不败作者>66.8售价>欲练此功&#xff0c;不必自宫!简介>辟邪剑谱书名>葵花宝典书名>书>九阴真经书名>独孤求败作者>88.9售价>武功狠!!!简介>书>书架>用递归将想找的标签名中的文本值找到&#xff1a;package myTestDom4j;…

您需要了解的关于React v17 0 Release候选的所有信息

Two years after launching React v16, the React team recently launched the first released candidate of React v17. Surprisingly, it doesn’t have any new features for developers. So what is v17? In this article, I will sum up all the latest changes done in …

java环境变量和用户变量_环境变量和用户变量有什么区别?

搭建编译环境时为什么有时候要设置环境变量&#xff0c;而有时又设置用户变量&#xff1f;环境变量分为系统环境变量和用户环境变量。你所说的环境变量是指系统环境变量&#xff0c;对所有用户起作用而用户环境变量只对当前用户起作用。例如你要用java&#xff0c;那么你把java…

react优化_优化您的React体验

react优化React is one of (if not the most) popular JavaScript library. There are good reasons why React is so beloved among Javascript developers. For one, it prioritizes the UI (User Interface) part of a website; this includes any aspect that a user inter…

java中br.readline_java中br.readLine与 br.read的用法区别

read方法功能&#xff1a;读取单个字符。 返回&#xff1a;作为一个整数(其范围从 0 到 65535 (0x00-0xffff))读入的字符&#xff0c;如果已到达流末尾&#xff0c;则返回 -1 readLine方法功能&#xff1a;读取一个文本行。通过下列字符之一即可认为某行已终止&#xff1a;换行…

仅使用javascript将样机转换为代码

AI is the future and it’s going to take all our jobs. You’ve probably heard that quite a bit as Machine Learning systems replace more and more traditional, manual, and repetitive jobs. You’ll also hear cries of the inevitable loss of entry level program…

java 发送短信计时_java中如何实现自动计时功能,就是点击一个start按钮就开始计时,以秒为单位...

展开全部简单代码如下&#xff1a;import java.awt.Button;import java.awt.FlowLayout;import java.awt.Label;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.text.SimpleDateFormat;import java.util.Date;import javax.swing.JFrame…

java filter 重定向_Java-不带重定向的Shiro过滤器

您需要实现自定义的Shiro过滤器.像这样&#xff1a;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import java.io.IOException ;import javax.servlet.http.HttpServletResponse ;import org.apache.shiro.web.filter.authz.AuthorizationFilter…