Virtual DOM
Course objectives
- What is the virtual DOM and what it does
- Basic use of Snabbdom
- Snabbdom source code parsing
What is the Virtual DOM
-
Virtual DOM(Virtual DOM) is a common JS object to describe the DOM object, because it is not a real DOM object, so it is called Virtual DOM
-
Real DOM members
let element = document.querySelector('#app') let s = '' for (var key in element) { s += key + ',' } console.log(s) // Print the result align,title,lang,translate,dir,hidden,accessKey,draggable,spellcheck,autocapitalize,contentEditable,isContentEditable,in putMode,offsetParent,offsetTop,offsetLeft,offsetWidth,offsetHeight,style,innerText,outerText,oncopy,oncut,onpaste,onabor t,onblur,oncancel,oncanplay,oncanplaythrough,onchange,onclick,onclose,oncontextmenu,oncuechange,ondblclick,ondrag,ondrag end,ondragenter,ondragleave,ondragover,ondragstart,ondrop,ondurationchange,onemptied,onended,onerror,onfocus,oninput,oni nvalid,onkeydown,onkeypress,onkeyup,onload,onloadeddata,onloadedmetadata,onloadstart,onmousedown,onmouseenter,onmouselea ve,onmousemove,onmouseout,onmouseover,onmouseup,onmousewheel,onpause,onplay,onplaying,onprogress,onratechange,onreset,on resize,onscroll,onseeked,onseeking,onselect,onstalled,onsubmit,onsuspend,ontimeupdate,ontoggle,onvolumechange,onwaiting, onwheel,onauxclick,ongotpointercapture,onlostpointercapture,onpointerdown,onpointermove,onpointerup,onpointercancel,onpo interover,onpointerout,onpointerenter,onpointerleave,onselectstart,onselectionchange,onanimationend,onanimationiteration ,onanimationstart,ontransitionend,dataset,nonce,autofocus,tabIndex,click,focus,blur,enterKeyHint,onformdata,onpointerraw update,attachInternals,namespaceURI,prefix,localName,tagName,id,className,classList,slot,part,attributes,shadowRoot,assi gnedSlot,innerHTML,outerHTML,scrollTop,scrollLeft,scrollWidth,scrollHeight,clientTop,clientLeft,clientWidth,clientHeight ,attributeStyleMap,onbeforecopy,onbeforecut,onbeforepaste,onsearch,elementTiming,previousElementSibling,nextElementSibli ng,children,firstElementChild,lastElementChild,childElementCount,onfullscreenchange,onfullscreenerror,onwebkitfullscreen change,onwebkitfullscreenerror,setPointerCapture,releasePointerCapture,hasPointerCapture,hasAttributes,getAttributeNames ,getAttribute,getAttributeNS,setAttribute,setAttributeNS,removeAttribute,removeAttributeNS,hasAttribute,hasAttributeNS,t oggleAttribute,getAttributeNode,getAttributeNodeNS,setAttributeNode,setAttributeNodeNS,removeAttributeNode,closest,match es,webkitMatchesSelector,attachShadow,getElementsByTagName,getElementsByTagNameNS,getElementsByClassName,insertAdjacentE lement,insertAdjacentText,insertAdjacentHTML,requestPointerLock,getClientRects,getBoundingClientRect,scrollIntoView,scro ll,scrollTo,scrollBy,scrollIntoViewIfNeeded,animate,computedStyleMap,before,after,replaceWith,remove,prepend,append,quer ySelector,querySelectorAll,requestFullscreen,webkitRequestFullScreen,webkitRequestFullscreen,createShadowRoot,getDestina tionInsertionPoints,ELEMENT_NODE,ATTRIBUTE_NODE,TEXT_NODE,CDATA_SECTION_NODE,ENTITY_REFERENCE_NODE,ENTITY_NODE,PROCESSIN G_INSTRUCTION_NODE,COMMENT_NODE,DOCUMENT_NODE,DOCUMENT_TYPE_NODE,DOCUMENT_FRAGMENT_NODE,NOTATION_NODE,DOCUMENT_POSITION_ DISCONNECTED,DOCUMENT_POSITION_PRECEDING,DOCUMENT_POSITION_FOLLOWING,DOCUMENT_POSITION_CONTAINS,DOCUMENT_POSITION_CONTAI NED_BY,DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC,nodeType,nodeName,baseURI,isConnected,ownerDocument,parentNode,parentEl ement,childNodes,firstChild,lastChild,previousSibling,nextSibling,nodeValue,textContent,hasChildNodes,getRootNode,normal ize,cloneNode,isEqualNode,isSameNode,compareDocumentPosition,contains,lookupPrefix,lookupNamespaceURI,isDefaultNamespace ,insertBefore,appendChild,replaceChild,removeChild,addEventListener,removeEventListener,dispatchEventCopy the code
-
You can use the Virtual DOM to describe the real DOM, for example
{ sel: "div", data: {}, children: undefined, text: "Hello Virtual DOM", elm: undefined, key: undefined } Copy the code
Why use the Virtual DOM
-
It is troublesome to operate the DOM manually, and browser compatibility issues also need to be considered. Although there are libraries such as jQuery to simplify DOM operations, the complexity of DOM operations increases with the complexity of projects
-
In order to simplify the complex manipulation of DOM, various MVVM frameworks have emerged to solve the problem of view and state synchronization
-
To simplify view manipulation we could use a template engine, but the problem of tracking state changes was not solved by the template engine, so the Virtual DOM emerged
-
The advantage of the Virtual DOM is that you don’t need to update the DOM immediately when the state changes. You just need to create a Virtual tree to describe the DOM. Inside the Virtual DOM, you will figure out how to update the DOM effectively
-
See the description of virtual-dom on Github
- The virtual DOM can maintain the state of the program, keeping track of the last state
- Update the real DOM by comparing the two states
The role of the virtual DOM
-
Maintain the relationship between views and states
-
Improved rendering performance in complex view situations
-
In addition to DOM rendering, it can also implement SSR(nuxt.js/Next-js), Native application (Weex/React Native), small program (MPvue /uni-app), etc
Virtual DOM library
-
Snabbdom
- The Virtual DOM used internally in Vue 2.x is a modified Snabbdom
- Approximately 200 SLOC (single line of code)
- Extensible through modules
- Source code is developed using TypeScript
- One of the fastest Virtual DOM
-
virtual-dom
Case presentation
- jQuery-demo
- snabbdom-demo
Snabbdom is basically used
Create a project
-
Package tools to facilitate the use of parcels
-
Create the project and install the Parcel
CD snabbdom-demo # package. Json NPM init -y # parcel NPM install parcel-bundler -DCopy the code
-
Configure scripts for package.json
"scripts": { "dev": "parcel index.html --open", "build": "parcel build index.html" } Copy the code
-
Creating a directory structure
│ ├ ─ garbage └─ SRC 01-basicusage.js │ index.html │ package.json ├ ─ basicusage.jsCopy the code
Import Snabbdom
Snabbdom document
-
Look at the meaning of the document
- Learn any library by first looking at the documentation
- Understand the library’s role through documentation
- See the examples provided in the documentation for a quick demo of your own
- View API usage through documentation
-
The document address
- Github.com/snabbdom/sn…
- The current version is V2.1.0
# --depth indicates the clone depth. 1 indicates that only the latest version is cloned. A lot of version because if project iteration, cloning will slow the git clone - b v2.1.0 - the depth = 1 https://github.com/snabbdom/snabbdom.gitCopy the code
Install Snabbdom
-
Install Snabbdom
NPM install [email protected]Copy the code
Import Snabbdom
-
The two core Snabbdom functions init and h()
- Init () is a higher-order function that returns patch()
- H () returns the virtual node VNode, which we saw when working with vue.js
import { init } from 'snabbdom/init'
import { h } from 'snabbdom/h'
const patch = init([])
Copy the code
Note: The path is not snabbdom/int. The path is set in the exports field of package.json. The package. exports field is not supported by the packaging tool we used. Webpack 4 also does not support this field, webPack 5 supports this field. When the field in the imported snabbdom/init will fill the full path into snabbdom/build/package/init. Js
"exports": {
"./init": "./build/package/init.js",
"./h": "./build/package/h.js",
"./helpers/attachto": "./build/package/helpers/attachto.js",
"./hooks": "./build/package/hooks.js",
"./htmldomapi": "./build/package/htmldomapi.js",
"./is": "./build/package/is.js",
"./jsx": "./build/package/jsx.js",
"./modules/attributes": "./build/package/modules/attributes.js",
"./modules/class": "./build/package/modules/class.js",
"./modules/dataset": "./build/package/modules/dataset.js",
"./modules/eventlisteners": "./build/package/modules/eventlisteners.js",
"./modules/hero": "./build/package/modules/hero.js",
"./modules/module": "./build/package/modules/module.js",
"./modules/props": "./build/package/modules/props.js",
"./modules/style": "./build/package/modules/style.js",
"./thunk": "./build/package/thunk.js",
"./tovnode": "./build/package/tovnode.js",
"./vnode": "./build/package/vnode.js"
}
Copy the code
-
If using a package.json exports field that does not support it, we should write out the module’s path completely
- View the directory structure of the installed SNabbDOM
import { h } from 'snabbdom/build/package/h'
import { init } from 'snabbdom/build/package/init'
import { classModule } from 'snabbdom/build/package/modules/class'
Copy the code
- Review the render function in Vue
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
Copy the code
- Thunk () is an optimization strategy that can be used when dealing with immutable data
Code demo
The basic use
Import {h} from 'snabbdom/build/package/h' import {init} from 'snabbdom/build/package/init / / using the init () function creates Patch () // init() is an array of arguments that can be passed to modules in the future, Let vnode = h('div. CLS ', [h('h1', 'Hello Snabbdom'), h('p', 'This is a paragraph ')]) const app = document.querySelector('#app') // Renders vNode to an empty DOM element (replace) // will return a new vNode let oldVnode = patch(app, vnode) setTimeout(() => { vnode = h('div.cls', [ h('h1', 'Hello World'), h('p', OldVnode = patch(oldVnode, vnode) // h('! ') is to create a comment patch(oldVnode, h('! '))}, 2000)Copy the code
The module
Snabbdom’s core library does not handle DOM element attributes/styles/events, etc. Modules can be used if needed
Commonly used modules
-
Six modules are officially available
-
attributes
- Set the attributes of the DOM element using
setAttribute
(a) - Handles properties of Boolean type
- Set the attributes of the DOM element using
-
props
- and
attributes
The module is similar to setting the attributes of the DOM elementelement[attr] = value
- Boolean type attributes are not handled
- and
-
class
- Switching class styles
- Note: Setting class styles for elements is done by
sel
The selector
-
dataset
- Set up the
data-*
Custom property of
- Set up the
-
eventlisteners
- Register and remove events
-
style
- Set inline styles to support animation
- delayed/remove/destroy
-
Module USES
-
Module usage steps:
- Import the required modules
- Register modules in init()
- When creating a VNode using the h() function, you can set the second parameter to the object and move the other parameters back
Code demo
Import {h} from 'snabbdom/build/package/h' import {init} from 'snabbdom/build/package/init/modules/import need import { styleModule } from 'snabbdom/build/package/modules/style' import { eventListenersModule } from 'snabbdom/build/package/modules/eventlisteners' / / using the init () function creates patch () / / init () parameter is an array, the future can be introduced into modules, Let patch = init([// register module styleModule, Let vnode = h('div. CLS ', {// Set the DOM element's inline style: {color: '#DEDEDE', backgroundColor: '#181A1B'}, // register event on: {click: clickHandler } }, [ h('h1', 'Hello Snabbdom'), h('p', 'This is a paragraph ')]) function clickHandler () {// This points to the corresponding vnode console.log(this.elm.innerhtml)}Copy the code
Snabbdom source code parsing
An overview of the
How to learn source code
- Start with the big picture
- Look at the source code with the target
- Look at the source code process to understand
- debugging
- The resources
The core of Snabbdom
- Use the h() function to create JavaScript objects (vNodes) that describe the real DOM
- Init () sets the module, creates the patch()
- Patch () compares the old and new vNodes
- Update the changed content to the real DOM tree
Snabbdom source
-
Source code address:
- Github.com/snabbdom/sn…
-
SRC directory structure
-
├ ─ ─ package │ ├ ─ ─ helpers │ │ └ ─ ─ attachto. Ts defines the vnode. Ts AttachData data structure of │ ├ ─ ─ modules │ │ ├ ─ ─ the attributes. The ts │ │ ├ ─ ─ Class. Ts │ │ ├── Dataset. Ts │ │ ├── Eventlisteners. Ts │ │ ├─ Hero │ │ ├ ─ ─ props. Ts │ │ └ ─ ─ style.css. Ts │ ├ ─ ─ h.t sh () function, │ ├─ HtmlDomAPI. Ts │ ├─ htmlDomAPI. │ ├─ ├─ JSX │ ├─ JSX │ ├─ JSX │ ├─ JSX │ ├─ JSX │ ├─ JSX │ ├─ JSX │ ├─ JSX │ ├─ For complex view immutable worth optimizing │ ├ ─ ─ tovnode. TsDOM converts VNode │ ├ ─ ─ ts - transform - js - extension. CJS │ ├ ─ ─ tsconfig. Jsonts compiler configuration file │ └ ─ ─ Vnode. ts Specifies the virtual nodeCopy the code
H function
-
Introduction to the h() function
-
You’ve seen the h() function in Vue
new Vue({ router, store, render: h => h(App) }).$mount('#app') Copy the code
-
The h() function, first seen in HyperScript, uses JavaScript to create hypertext
-
Instead of creating hypertext, the h() function in Snabbdom creates vNodes
-
-
Function overloading
-
concept
- A function with a different number of arguments or type
- There is no concept of overloading in JavaScript
- Overloading exists in TypeScript, but overloading is implemented by adjusting parameters in code
-
Hint of overload
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(1, 2, 3) Copy the code
function add (a: number, b: number) { console.log(a + b) } function add (a: number, b: string) { console.log(a + b) } add(1, 2) add(1, '2') Copy the code
- Source location: SRC /package/h.ts
// overloading the h function export function h (sel: string) :VNode export function h (sel: string, data: VNodeData | null) :VNode export function h (sel: string, children: VNodeChildren) :VNode export function h (sel: string, data: VNodeData | null, children: VNodeChildren) :VNode export function h (sel: any, b? : any, c? : any) :VNode { var data: VNodeData = {} var children: any var text: any var i: number// Handle arguments to implement overloading mechanismsif (c ! = =undefined) {// handle three arguments //sel,data,children/text if (b ! = =null) { data = b } if (is.array(c)) { children = c } else if (is.primitive(c)) { text = c } else if (c && c.sel) { children = [c] } } else if(b ! = =undefined&& b ! = =null) { if (is.array(b)) { children = b } else if (is.primitive(b)) { // If c is a string or number text = b } else if (b && b.sel) { // If b is VNode children = [b] } else { data = b } } if(children ! = =undefined) { // Handle primitive values in children (string/number) for (i = 0; i < children.length; ++i) { // If child is string/number, create a text node if (is.primitive(children[i])) children[i] = vnode(undefined.undefined.undefined, children[i], undefined)}}if ( sel[0= = ='s' && sel[1= = ='v' && sel[2= = ='g' && (sel.length === 3 || sel[3= = ='. ' || sel[3= = =The '#')) {// If SVG is used, add namespace addNS(data, children, sel) } / / return VNode return vnode(sel, data, children, text, undefined)};Copy the code
-
VNode
- A VNode is a Virtual node that describes a DOM element. If the VNode has children, it is a Virtual DOM
- SRC /package/vnode.ts
export interface VNode {
/ / selector
sel: string | undefined;
// Node data: attributes/styles/events etc
data: VNodeData | undefined;
// Child nodes, and text are mutually exclusive
children: Array<VNode | string> | undefined;
// Record the actual DOM corresponding to the vNode
elm: Node | undefined;
// The contents of this node are mutually exclusive with children
text: string | undefined;
/ / optimization
key: Key | undefined;
}
export function vnode (sel: string | undefined,
data: any | undefined,
children: Array<VNode | string> | undefined,
text: string | undefined,
elm: Element | Text | undefined) :VNode {
const key = data === undefined ? undefined : data.key
return { sel, data, children, text, elm, key }
}
Copy the code
snabbdom
- patch(oldVnode, newVnode)
- Patch, render the changed content of the new node into the real DOM, and finally return the new node as the old node for the next processing
- Check whether the old and new VNodes have the same key and SEL.
- If it is not the same node, delete the previous content and re-render
- If the node is the same, check whether the new VNode has a text. If the text is different from that of the oldVnode, update the text directly
- If the new VNode has children, the diff algorithm is used to determine whether the child node has changed
- The diff procedure only makes same-level comparisons
init
-
Init (modules, domApi), return patch() function (higher order function)
-
Why use higher-order functions?
- Because patch() is called externally multiple times, each call depends on arguments such as modules/domApi/ CBS
- Init () can be closed internally by higher-order functions, and the returned patch() can access modules/domApi/ CBS without needing to be recreated
-
Init () first collects all of the module’s hook functions and stores them in a CBS object before returning patch()
-
SRC /package/init.ts
const hooks: Array<keyof Module> = ['create'.'update'.'remove'.'destroy'.'pre'.'post']
export function init (modules: Array<Partial<Module>>, domApi? : DOMAPI) {
let i: number
let j: number
const cbs: ModuleHooks = {
create: [].update: [].remove: [].destroy: [].pre: [].post: []}// Initialize the API
constapi: DOMAPI = domApi ! = =undefined ? domApi : htmlDomApi
// Store all incoming module hook methods in a CBS object
CBS = [create: [fn1, fn2], update: [],...
for (i = 0; i < hooks.length; ++i) {
// cbs['create'] = []
cbs[hooks[i]] = []
for (j = 0; j < modules.length; ++j) {
// const hook = modules[0]['create']
const hook = modules[j][hooks[i]]
if(hook ! = =undefined) {
(cbs[hooks[i]] asAny []).push(hook)}}}......return function patch (oldVnode: VNode | Element, vnode: VNode) :VNode {... }}Copy the code
patch
-
Function:
- Pass in the old and new VNodes, compare the differences, and render the differences into the DOM
- Return the new VNode as the oldVnode for the next patch()
-
Execution process:
-
Start by executing the hook function pre in the module
-
If oldVnode and VNode are the same (same key and SEL)
- Call patchVnode() to find the node differences and update the DOM
-
If oldVnode is a DOM element
- Convert DOM elements into OLdvNodes
- Call createElm() to convert vNode to a real DOM and log to vNode.elm
- Insert the newly created DOM element into the parent
- Removing an old node
- The triggerThe userSet up the
create
hookfunction
-
-
SRC /package/init.ts
return function patch (oldVnode: VNode | Element, vnode: VNode) :VNode {
let i: number, elm: Node, parent: Node
// Save the queue of newly inserted nodes in order to trigger the hook function
const insertedVnodeQueue: VNodeQueue = []
// Execute the module's Pre hook function
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()
// If oldVnode is not VNode, create VNode and set elm
if(! isVnode(oldVnode)) {// Convert the DOM element to an empty VNode
oldVnode = emptyNodeAt(oldVnode)
}
// If the new and old nodes are the same node (key and SEL are the same)
if (sameVnode(oldVnode, vnode)) {
// Find the node differences and update the DOM
patchVnode(oldVnode, vnode, insertedVnodeQueue)
} else {
// If the old and new nodes are different, vNode creates the corresponding DOM
// Get the current DOM element
elm = oldVnode.elm!
parent = api.parentNode(elm) as Node
// Trigger init/create hook function to create DOM
createElm(vnode, insertedVnodeQueue)
if(parent ! = =null) {
// If the parent node is not empty, insert the DOM corresponding to the vnode into the documentapi.insertBefore(parent, vnode.elm! , api.nextSibling(elm))// Remove the old node
removeVnodes(parent, [oldVnode], 0.0)}}// Execute the user-set insert hook function
for (i = 0; i < insertedVnodeQueue.length; ++i) { insertedVnodeQueue[i].data! .hook! .insert! (insertedVnodeQueue[i]) }// Execute the module's POST hook function
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]()
return vnode
}
Copy the code
createElm
-
Function:
- CreateElm (vnode, insertedVnodeQueue) returns the created DOM element
- Create the DOM element corresponding to the vNode
-
Execution process:
-
The user-set init hook function is triggered first
-
If the selector is! To create a comment node
-
If the selector is empty, a text node is created
-
If the selector is not empty
- Parse the selector and set the id and class properties of the tag
- Execute the module’s CREATE hook function
- If the vnode has children, create a DOM corresponding to the child vNode and append it to the DOM tree
- If the vNode text value is string/number, create a text node and chase it into the DOM tree
- Execute the user-set CREATE hook function
- If there is a user-set insert hook function, add vNode to the queue
-
-
SRC /package/init.ts
function createElm (vnode: VNode, insertedVnodeQueue: VNodeQueue) :Node {
let i: any
let data = vnode.data
if(data ! = =undefined) {
// Execute the user-set init hook function
constinit = data.hook? .initif (isDef(init)) {
init(vnode)
data = vnode.data
}
}
const children = vnode.children
const sel = vnode.sel
if (sel === '! ') {
// If the selector is! To create a comment node
if (isUndef(vnode.text)) {
vnode.text = ' '
}
vnode.elm = api.createComment(vnode.text!)
} else if(sel ! = =undefined) {
// If the selector is not empty
// Parse the selector
// Parse selector
const hashIdx = sel.indexOf(The '#')
const dotIdx = sel.indexOf('. ', hashIdx)
const hash = hashIdx > 0 ? hashIdx : sel.length
const dot = dotIdx > 0 ? dotIdx : sel.length
consttag = hashIdx ! = = -1|| dotIdx ! = = -1 ? sel.slice(0.Math.min(hash, dot)) : sel
const elm = vnode.elm = isDef(data) && isDef(i = data.ns)
? api.createElementNS(i, tag)
: api.createElement(tag)
if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot))
if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g.' '))
// Execute the module's CREATE hook function
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode)
// If there are child nodes in the vNode, create the DOM element corresponding to the child vnode and append it to the DOM tree
if (is.array(children)) {
for (i = 0; i < children.length; ++i) {
const ch = children[i]
if(ch ! =null) {
api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue))
}
}
} else if (is.primitive(vnode.text)) {
// If the vNode text value is string/number, create a text node and append it to the DOM tree
api.appendChild(elm, api.createTextNode(vnode.text))
}
consthook = vnode.data! .hookif (isDef(hook)) {
// Execute the user passed hook createhook.create? .(emptyNode, vnode)if (hook.insert) {
// Add the vNode to the queue in preparation for the insert hook
insertedVnodeQueue.push(vnode)
}
}
} else {
// If the selector is empty, create a text node
vnode.elm = api.createTextNode(vnode.text!)
}
// Returns the newly created DOM
return vnode.elm
}
Copy the code
patchVnode
-
Function:
- patchVnode(oldVnode, vnode, insertedVnodeQueue)
- Compare oldVnode and VNode differences and render the differences into the DOM
-
Execution process:
-
The user-set prePatch hook function is first executed
-
Execute the CREATE hook function
- The module’s CREATE hook function is first executed
- The user-set CREATE hook function is then executed
-
If vnode.text is not defined
-
If oldvNode. children and vnode.children both have values
- call
updateChildren()
- Diff algorithm is used to compare and update child nodes
- call
-
If vnode.children has a value, oldvNode. children has no value
- Clearing DOM elements
- call
addVnodes()
To add child nodes in batches
-
If oldvNode. children has a value, vnode.children has no value
- call
removeVnodes()
To remove child nodes in batches
- call
-
If oldvNode. text has a value
- Empty the content of the DOM element
-
-
If vnode.text is set and differs from oldvNode. text
- If the old node has children, remove them all
- To set the DOM element
textContent
为vnode.text
-
Finally, execute the user-set PostPatch hook function
-
-
SRC /package/init.ts
function patchVnode (oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
consthook = vnode.data? .hook// Execute the user-set prepatch hook function firsthook? .prepatch? .(oldVnode, vnode)const elm = vnode.elm = oldVnode.elm!
const oldCh = oldVnode.children as VNode[]
const ch = vnode.children as VNode[]
// Return if the old and new vNodes are the same
if (oldVnode === vnode) return
if(vnode.data ! = =undefined) {
// Execute the module's update hook function
for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
// Execute the user-set update hook functionvnode.data.hook? .update? .(oldVnode, vnode) }// If vnode.text is not defined
if (isUndef(vnode.text)) {
// If both new and old nodes have children
if (isDef(oldCh) && isDef(ch)) {
// Call updateChildren to compare and update child nodes
if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue) }else if (isDef(ch)) {
// If the new node has children, the old node has no children
// Empty the dom element if the old node has text
if (isDef(oldVnode.text)) api.setTextContent(elm, ' ')
// Add child nodes in batches
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
// If the old node has children, the new node has no children
// Remove child nodes in batches
removeVnodes(elm, oldCh, 0, oldCh.length - 1)}else if (isDef(oldVnode.text)) {
// If the old node has text, clear the DOM element
api.setTextContent(elm, ' ')}}else if(oldVnode.text ! == vnode.text) {// If vnode.text is not set
if (isDef(oldCh)) {
// If the old node has children, remove it
removeVnodes(elm, oldCh, 0, oldCh.length - 1)}// Set the DOM element's textContent to vnode.text
api.setTextContent(elm, vnode.text!)
}
// Finally execute the user-set postpatch hook functionhook? .postpatch? .(oldVnode, vnode) }Copy the code
-
When comparing nodes of the same level, the start and end nodes of the new and old node arrays will be marked with index, and the index will be moved during traversal
-
When comparing the start and end nodes, there are four cases
- OldStartVnode/newStartVnode (old start node/new start node)
- OldEndVnode/newEndVnode (old end node/new end node)
- OldStartVnode/oldEndVnode (old start node/new end node)
- OldEndVnode/newStartVnode (old end node/new start node)
-
The start node is compared to the end node. The two cases are similar
- OldStartVnode/newStartVnode (old start node/new start node)
- OldEndVnode/newEndVnode (old end node/new end node)
-
If oldStartVnode and newStartVnode are samevNodes (same key and SEL)
- Call patchVnode() to compare and update nodes
- Move the old start and new start indexes back oldStartIdx++ / oldEndIdx++
-
OldStartVnode/newEndVnode (old start node/new end node) same
- Call patchVnode() to compare and update nodes
- Move the oldStartVnode DOM element to the right – update the index
-
OldEndVnode/newStartVnode (old end node/new start node) same
- Call patchVnode() to compare and update nodes
- Move the oldEndVnode DOM element to the left
- Update the index
-
If it’s not four of the above
-
Iterate over the new node, using the newStartNode key to find the same node in the old node array
-
If not, newStartNode is the new node
- Create a DOM element corresponding to the new node and insert it into the DOM tree
-
If we find it,
-
Determine whether the SEL selector of the new node is the same as that of the old node found
-
If they are different, the nodes are modified
- Re-create the corresponding DOM element and insert it into the DOM tree
-
If so, move the DOM element corresponding to elmToMove to the left
-
-
-
End of the cycle
- The loop ends when all children of the old node are traversed first (oldStartIdx > oldEndIdx)
- All children of the new node are traversed first (newStartIdx > newEndIdx), and the loop ends
-
If the array of the old node is traversed first (oldStartIdx > oldEndIdx), the new node has a surplus, and the remaining nodes are batch inserted to the right
- If the array of the new node is traversed first (newStartIdx > newEndIdx), it indicates that the old node has surplus. Delete the remaining nodes in batches
SRC /package/init.ts
function updateChildren (parentElm: Node, oldCh: VNode[], newCh: VNode[], insertedVnodeQueue: VNodeQueue) {
let oldStartIdx = 0
let 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: KeyToIndexMap | undefined
let idxInOld: number
let elmToMove: VNode
let before: any
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
// After index changes, the node may be set to null
if (oldStartVnode == null) {
// The node is an empty move index
oldStartVnode = oldCh[++oldStartIdx] // Vnode might have been moved left
} else if (oldEndVnode == null) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (newStartVnode == null) {
newStartVnode = newCh[++newStartIdx]
} else if (newEndVnode == null) {
newEndVnode = newCh[--newEndIdx]
// Compare the four cases of start and end nodes
} else if (sameVnode(oldStartVnode, newStartVnode)) {
// 1. Compare the old start node with the new start node
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
// 2. Compare the old end node with the new end node
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
// 3. Compare the old start node with the new end nodepatchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) api.insertBefore(parentElm, oldStartVnode.elm! , api.nextSibling(oldEndVnode.elm!) ) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] }else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
// 4. Compare the old end node with the new start nodepatchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) api.insertBefore(parentElm, oldEndVnode.elm! , oldStartVnode.elm!) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] }else {
// The start node and the end node are different
// Use newStartNode's key to find the same node in the old node array
// Set the key and index objects
if (oldKeyToIdx === undefined) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
}
// Iterate over newStartVnode to find the oldVnode index with the same key from the old node
idxInOld = oldKeyToIdx[newStartVnode.key as string]
// If it is a new VNode
if (isUndef(idxInOld)) { // New element
// If not found, newStartNode is the new node
// Create an element to insert into the DOM tree
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm!)
} else {
// If an old node with the same key is found, record it in elmToMove traversal
elmToMove = oldCh[idxInOld]
if(elmToMove.sel ! == newStartVnode.sel) {// If the old and new nodes have different selectors
// Create a DOM element corresponding to the new start node and insert it into the DOM tree
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm!)
} else {
// If same, patchVnode()
// Move the elmToMove element to the left
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined as any
api.insertBefore(parentElm, elmToMove.elm!, oldStartVnode.elm!)
}
}
// Re-assign newStartVnode to the next new node
newStartVnode = newCh[++newStartIdx]
}
}
// When the loop ends, the old node array completes first or the new node array completes first
if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {
if (oldStartIdx > oldEndIdx) {
// If the old node array is traversed first, new nodes are left
// Insert the remaining new nodes to the right
before = newCh[newEndIdx + 1] = =null ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else {
// If the new node array is traversed first, the old node is left
// Delete old nodes in batches
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
}
Copy the code