Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to convert Vue Virtual Dom to Real Dom

2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/01 Report--

This article mainly explains the "Vue virtual Dom and real Dom how to convert", the content of the article is simple and clear, easy to learn and understand, the following please follow the editor's ideas slowly in depth, together to study and learn "Vue virtual Dom and real Dom how to convert" it!

After we have another Javascript object with a tree structure, all we need to do is to map the tree to the real Dom tree. Let's review the mountComponnet method first:

Export function mountComponent (vm, el) {vm.$el = el... CallHook (vm, "beforeMount")... Const updateComponent = function () {vm._update (vm._render ())}...}

Now that we have finished executing the vm._render method and getting the VNode, we now pass it as an argument to the vm._update method and execute it. The purpose of the vm._update method is to turn VNode into a real Dom, but it has two timing of execution:

Render for the first time

When the new Vue is executed for the first time, the incoming Vnode object is mapped to the real Dom.

Update page

Data changes will drive page changes, which is also one of the most unique features of vue. Two VNode are generated before and after data changes are compared, and how to make minimal changes on the old VNode to render the page, such a diff algorithm is still very complex. Using diff directly is not good for understanding the overall process of vue without first explaining what the data response is all about. So after this chapter analyzes the first rendering, the next chapter is the data response, followed by the diff comparison.

Let's take a look at the definition of the vm._update method:

Vue.prototype._update = function (vnode) {... First render vm.$el = vm.__patch__ (vm.$el, vnode) / / overwrite the original vm.$el.}

Vm here. E l is mounted in the = = m o u n t C o m p o n e n t = square method before, a real = = D o m = = element. The first dye will be transmitted to v m. El is previously mounted in the = = mountComponent== method, a real = = Dom== element. Vm will be passed into the first rendering. El is previously mounted in the = = mountComponent== method, a real = = Dom== element. Vm.el and the resulting VNode will be passed in for the first rendering, so take a look at the vm.patch definition:

Vue.prototype.__patch__ = createPatchFunction ({nodeOps, modules})

Patch is a method returned inside the createPatchFunction method that accepts an object:

NodeOps attribute: encapsulates a collection of methods for manipulating native Dom, such as creating, inserting, and removing these, which we will explain in detail where we use them.

Modules attribute: to create a real Dom, you also need to generate its attributes such as class/attrs/style. Modules is a collection of arrays, and each item in the array is the corresponding hook method for these attributes. There are corresponding hook methods for the creation, update, destruction, and so on of these attributes. When you need to do something at a certain time, just execute the corresponding hook. For example, they all have create hook methods, such as collecting these create hooks into an array, and when you need to create these properties on the real Dom, execute each item of the array in turn, that is, create them in turn.

PS: here the hook method in the modules attribute distinguishes the platform. Web, weex and SSR do not call the VNode method in the same way, so vue uses the function Corrigenization operation here to smooth out the platform differentiation in the createPatchFunction, so the patch method only needs to receive the new and old node.

Generate Dom

Just remember that no matter what type of node VNode is, only three types of nodes will be created and inserted into the Dom: element node, comment node, and text node.

Let's take a look at what kind of method createPatchFunction returns:

Export function createPatchFunction (backend) {... Const {modules, nodeOps} = backend / / deconstruct the incoming collection return function (oldVnode, vnode) {/ / receive the old and new vnode. Const isRealElement = isDef (oldVnode.nodeType) / / whether it is real Dom if (isRealElement) {/ / $el is real Dom oldVnode = emptyNodeAt (oldVnode) / / convert to VNode format to overwrite yourself}.}}

There is no oldVnode when rendering for the first time. OldVnode is $el, a real dom, wrapped by the emptyNodeAt (odVnode) method:

Function emptyNodeAt (elm) {return new VNode (nodeOps.tagName (elm) .toLowerCase (), / / corresponding tag attribute {}, / corresponding data [], / / corresponding children undefined, / / corresponding text elm / / Real dom assigned to elm attribute)} wrapped: {tag: "div" Elm: "" / / Real dom}-nodeOps:export function tagName (node) {/ / returns the tag name of the node return node.tagName}

After changing the = = $el== attribute passed in to VNode format, let's continue:

Export function createPatchFunction (backend) {... Return function (oldVnode, vnode) {/ / receive new and old vnode const insertedVnodeQueue = []. Const oldElm = oldVnode.elm / / wrapped real Dom const parentElm = nodeOps [XSS _ clean] (oldElm) / / the first parent node is createElm (/ / create real Dom vnode, / / second parameter insertedVnodeQueue, / / empty array parentElm / / nodeOps.nextSibling (oldElm) / / next node) return vnode.elm / / returns the real Dom overlay vm.$el}}-- -nodeOps:export function parentNode (node) {/ / get the parent node return node [XSS _ clean]} export function nextSibling (node) {/ / get the next node return node.nextSibing}

The createElm method starts to generate real Dom, and the way VNode generates real Dom is still divided into element nodes and components, so we use the VNode generated in the previous chapter.

1. Element nodes generate Dom {/ / element nodes VNode tag: "div", children: [{tag: "H1", children: [{text: "title H1"}]}, {tag: "H2", children: [{text: "title H2"}]}, {tag: "h3" Children: [{text: "title h3"}]}}

You can first take a look at this flow chart to have an impression, and then look at the specific implementation of the idea will be much clearer (here first borrow a picture on the Internet):

To start Dom, let's take a look at its definition:

Function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {. Const children = vnode.children / / [VNode, VNode, VNode] const tag = vnode.tag / / div if (createComponent (vnode, insertedVnodeQueue, parentElm, refElm)) {return / / if the component result returns true, it will not continue Then explain createComponent} if (isDef (tag)) {/ / element node vnode.elm = nodeOps.createElement (tag) / / create parent node createChildren (vnode, children, insertedVnodeQueue) / / create child node insert (parentElm, vnode.elm) RefElm) / / insert} else if (isTrue (vnode.isComment)) {/ / comment node vnode.elm = nodeOps.createComment (vnode.text) / / create comment node insert (parentElm, vnode.elm, refElm) / / insert to parent node} else {/ / text node vnode.elm = nodeOps.createTextNode (vnode.text) / / create text node insert (parentElm, vnode.elm) RefElm) / / insert into parent node}...}-nodeOps:export function createElement (tagName) {/ / create node return document.createElement (tagName)} export function createComment (text) {/ / create comment node return document.createComment (text)} export function createTextNode (text) {/ / create text node return document.createTextNode (text)} function insert (parent Elm, ref) {/ / insert dom operation if (isDef (parent)) {/ / have a parent if (isDef (ref)) {/ / have a reference node if (ref[ XSS _ clean] = parent) {/ / the parent node of the reference node is equal to the incoming parent node nodeOps.insertBefore (parent, elm) Ref) / / insert elm}} else {nodeOps.appendChild (parent, elm) / / add elm to parent}} / / do nothing} before the reference node within the parent node. This is an important method. Because it's used in a lot of places.

Determine whether it is an element node, a comment node, and a text node in turn, create them respectively and insert them into the parent node, here mainly introduces the creation of element nodes, the other two do not have complex logic. Let's take a look next: the createChild method definition:

Function createChild (vnode, children, insertedVnodeQueue) {if (Array.isArray (children)) {/ / is the array for (let I = 0; I)

< children.length; ++i) { // 遍历vnode每一项 createElm( // 递归调用 children[i], insertedVnodeQueue, vnode.elm, null, true, // 不是根节点插入 children, i ) } } else if(isPrimitive(vnode.text)) { //typeof为string/number/symbol/boolean之一 nodeOps.appendChild( // 创建并插入到父节点 vnode.elm, nodeOps.createTextNode(String(vnode.text)) ) }}-------------------------------------------------------------------------------nodeOps:export default appendChild(node, child) { // 添加子节点 node.appendChild(child)} 开始创建子节点, 遍历VNode 的每一项, 每一项还是使用之前的createElm方法创建Dom。 如果某一项又是数组,继续调用createChild创建某一项的子节点; 如果某一项不是数组, 创建文本节点并将它添加到父节点内。 像这样使用递归的形式将嵌套的VNode全部创建为真实的Dom。 在看一遍流程图, 应该就能减少大家很多疑惑了(这里先借用网上一章图): 简单来说就是由里向外的挨个创建出真实的Dom, 然后插入到它的父节点内,最后将创建好的Dom插入到body内, 完成创建的过程, 元素节点的创建还是比较简单的, 接下来看下组件式怎么创建的。 组件VNode生成Dom{ // 组件VNode tag: "vue-component-1-app", context: {...}, componentOptions: { Ctor: function(){...}, // 子组件构造函数 propsData: undefined, children: undefined, tag: undefined }, data: { on: undefined, // 原生事件 hook: { // 组件钩子 init: function(){...}, insert: function(){...}, prepatch: function(){...}, destroy: function(){...} } }}------------------------------------------- // app组件内模板 app text 首先看张简易流程图, 留个影响即可,方便理清之后的逻辑顺序(这里借用网上一张图): 使用上一章组件生成VNode , 看下在createElm 内创建组件Dom分支逻辑是怎么样的: function createElm(vnode, insertedVnodeQueue, parentElm, refElm) { ... if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { // 组件分支 return } ... 执行createComponent 方法, 如果是元素节点不会返回任何东西,所以是undefined , 会继续走接下来的创建元节点的逻辑。 现在是组件, 我们看下createComponent 的实现: function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) { let i = vnode.data if(isDef(i)) { if(isDef(i = i.hook) && isDef(i = i.init)) { i(vnode) // 执行init方法 } ... }} 首先会将组件的vnode.data赋值给i, 是否有这个属性就能判断是否是组件vnode。 之后的if(isDef(i = i.hook) && isDef(i = i.init)) 集判断和赋值为一体, if 内的i(vnode) 就是执行的组件init(vnode)方法。 这个时候我们来看下组件的init 钩子方法做了什么: import activeInstance // 全局变量const init = vnode =>

{const child = vnode.componentInstance = createComponentInstanceForVnode (vnode, activeInstance).}

ActiveInstance is a global variable, and then the value is assigned to the current instance in the update method, and then the current instance is passed in as the parent instance of the component in the process of patch, and the component relationship is built in the initLifecycle of the child component. The result of createComponentInsanceForVnode execution is assigned to vnode.componentInstance, so take a look at what it returns:

Export createComponentInstanceForVnode (vnode, parent) {/ / parent is the global variable activeInstance const options = {/ / component's options _ isComponent: true, / / sets a flag bit to indicate that it is the parent vm instance of the component _ parentVnode: vnode, parent / / child component, so that the initialization initLifecycle can establish a parent-child relationship} return new vnode.componentOptions.Ctor (options) / / the constructor of the child component is defined as Ctor}

The craeeteComponentInstanceForVnode method is executed first in the init method of the component, and the inner part of this method instantiates the constructor of the subcomponent, because the constructor of the subcomponent inherits all the capabilities of the base class Vue, which is equivalent to executing new Vue ({... }), and then execute the = = _ init method to initialize a series of subcomponents and go back to the _ init== method, because there are still some differences between them:

Vue.prototype._init = function (options) {if (options & & options._isComponent) {/ / the merge options,_isComponent of the component is distinguished by the previously defined tag bit initInternalComponent (this, options) / / because the merge item of the component is much simpler} initLifecycle (vm) / / to establish a parent-child relationship. CallHook (vm, "created") if (vm.$options.el) {/ / component has no el attribute So how to stop here vm.$mount (vm.$options.el)}}-function initInternalComponent (vm) Options) {/ / merge subcomponent options const opts = vm.$options = Object.create (vm.constructor.options) opts.parent = options.parent / / component init assignment Global variable activeInstance opts._parentVnode = options._parentVnode / / component init assignment, component vnode.}

The previous execution is all right, but in the end, because there is no el attribute, it is not mounted, and the createComponentInstanceForVnode method is finished. At this point we go back to the component's init method to complete the rest of the logic:

Const init = vnode = > {const child = vnode.componentInstance = / / get the instance of the component createComponentInstanceForVnode (vnode, activeInstance) child.$mount (undefined) / / then mount it manually}

We manually mount the component in the init method, and then execute the component's = = render () = method to get the element node VNode in the component, and then execute vm._update () to execute the component's patch method, because the $mount method passes in undefined, and oldVnode is also undefinned, which executes the logic in _ _ patch_:

Return function patch (oldVnode, vnode) {... If (isUndef (oldVnode)) {createElm (vnode, insertedVnodeQueue)}...}

This execution of createElm does not pass in the parent node of the third parameter, so where does the Dom created by the component take effect? If there is no parent node page to generate Dom, the patch of the component is executed at this time, so the parameter vnode is the vnode of the element node in the component:

/ / element vnode tag: "div", children: [{text: app text}], parent: {/ / subcomponent _ init in template app text- {/ / app in app component tag: "vue-component-1-app", componentOptions: {...}

It is obvious that it is not a component at this time, even if it is a component, it is better to execute the logic of createComponent to create a component, because there will always be components made up of element nodes. At this point we perform the logic of creating the element node, because there is no third parameter parent node, so the Dom of the component is created and will not be inserted here. Notice that the init of the component is complete at this time, but the createComponent method of the component is not complete, so we complete its logic:

Function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {let I = vnode.data If (isDef (I)) {if (isDef (I = i.hook) & & isDef (I = i.init)) {I (vnode) / / init has been completed} if (isDef (vnode.componentInstance)) {/ / assign initComponent (vnode) / / assign real dom to vnode.elm insert (parentElm, vnode.elm) when executing component init RefElm) / / component Dom is inserted here. Return true / so will directly return}}-function initComponent (vnode) {. Vnode.elm = real dom returned by vnode.componentInstance.$el / / _ _ patch__.}

No matter how deeply nested the component is, execute init after meeting the component, and encounter the nested component in the patch process of init, then execute the init of the nested component. After completing the _ _ patch__, the nested component inserts the real Dom into its parent node, then inserts the patch of the outer component into its parent points, and finally inserts it into the body to complete the creation process of the nested component. In short, it is still a process from the inside out.

Looking back at this picture, I believe it will be easy to understand:

Then complete the logic after the original mountComponent in this chapter:

Export function mountComponent (vm, el) {... Const updateComponent = () = > {vm._update (vm._render ())} new Watcher (vm, updateComponent, noop, {before () {if (vm._isMounted) {callHook (vm, "beforeUpdate")}, true). CallHook (vm, "mounted") return vm}

Next, we will pass updateComponent into a class of Watcher, what this class is for, which we will describe in the next chapter. Next, execute the mounted hook method. At this point, the whole process of new vue is over. Let's review the order of execution starting with new Vue:

New Vue = > vm._init () = = > vm.$mount (el) = = > vm._render () = = > vm.update (vnode)

Finally, we conclude this chapter with a question:

The parent and son components define four hooks: beforeCreate, created, beforeMounte and mounted at the same time. What is the order in which they are executed?

Answer:

The initialization process of the parent component will be performed first, so beforeCreate and created will be executed in turn, and the beforeMount hook will be executed before the mount is performed. However, when the nested sub-component is encountered in the process of generating the _ _ patch__ of the real dom, it will be converted to execute the initialization hook beforeCreate, created of the sub-component. The sub-component will execute beforeMounte before mounting, and then execute mounted after the creation of the Dom of the sub-component. The patch process of this parent component is complete, and finally the mounted hook of the parent component is executed, which is the order in which they are executed. As follows:

Parent beforeCreateparent createdparent beforeMounte child beforeCreate child created child beforeMounte child mountedparent mounted thank you for reading, the above is the content of "how to convert Vue virtual Dom and real Dom". After the study of this article, I believe you have a deeper understanding of how to convert Vue virtual Dom and real Dom, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report