Heim > Artikel > Web-Frontend > Implementieren Sie den virtuellen Dom-Patch in Vue (ausführliches Tutorial)
Dieser Artikel stellt hauptsächlich die Patch-Quellcode-Analyse von Vue Virtual Dom vor. Jetzt teile ich ihn mit Ihnen und gebe Ihnen eine Referenz.
Dieser Artikel stellt die Patch-Quellcode-Analyse von Vue Virtual Dom vor und teilt sie mit allen. Die Details lauten wie folgt:
Quellcode-Verzeichnis: src/core/vdom/patch.js
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { 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, idxInOld, vnodeToMove, refElm const canMove = !removeOnly while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { // 开始索引大于结束索引,进不了 if (isUndef(oldStartVnode)) { oldStartVnode = oldCh[++oldStartIdx] // Vnode已经被移走了。 } else if (isUndef(oldEndVnode)) { oldEndVnode = oldCh[--oldEndIdx] } else if (sameVnode(oldStartVnode, newStartVnode)) { patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) oldStartVnode = oldCh[++oldStartIdx] // 索引加1。是去对比下一个节点。比如之前start=a[0],那现在start=a[1],改变start的值后再去对比start这个vnode newStartVnode = newCh[++newStartIdx] } else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldStartVnode, newEndVnode)) { patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))// 把节点b移到树的最右边 oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldEndVnode, newStartVnode)) { old.end.d=new.start.d patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)// Vnode moved left,把d移到c的左边。=old.start->old.end oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] } else { if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) if (isUndef(idxInOld)) { createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm) // 创建新节点,后面执行了nodeOps.insertBefore(parent, elm, ref) } else { vnodeToMove = oldCh[idxInOld] /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !vnodeToMove) { warn( 'It seems there are duplicate keys that is causing an update error. ' + 'Make sure each v-for item has a unique key.' ) } if (sameVnode(vnodeToMove, newStartVnode)) { patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue) oldCh[idxInOld] = undefined canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) } else { // same key but different element. treat as new element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm) } } newStartVnode = newCh[++newStartIdx] } } if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) // 删除旧的c,removeNode(ch.elm) } }
function sameVnode (a, b) { return ( a.key === b.key && ( ( a.tag === b.tag && a.isComment === b.isComment && isDef(a.data) === isDef(b.data) && sameInputType(a, b) ) || ( isTrue(a.isAsyncPlaceholder) && a.asyncFactory === b.asyncFactory && isUndef(b.asyncFactory.error) ) ) ) } /** * 比较新旧vnode节点,根据不同的状态对dom做合理的更新操作(添加,移动,删除)整个过程还会依次调用prepatch,update,postpatch等钩子函数,在编译阶段生成的一些静态子树,在这个过程 * @param oldVnode 中由于不会改变而直接跳过比对,动态子树在比较过程中比较核心的部分就是当新旧vnode同时存在children,通过updateChildren方法对子节点做更新, * @param vnode * @param insertedVnodeQueue * @param removeOnly */ function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) { if (oldVnode === vnode) { return } const elm = vnode.elm = oldVnode.elm if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue) } else { vnode.isAsyncPlaceholder = true } return } // 用于静态树的重用元素。 // 注意,如果vnode是克隆的,我们只做这个。 // 如果新节点不是克隆的,则表示呈现函数。 // 由热重加载api重新设置,我们需要进行适当的重新渲染。 if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance return } let i const data = vnode.data if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode) } const oldCh = oldVnode.children const ch = vnode.children if (isDef(data) && isPatchable(vnode)) { for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode) } if (isUndef(vnode.text)) { if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) } else if (isDef(ch)) { if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1) } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, '') } } else if (oldVnode.text !== vnode.text) { nodeOps.setTextContent(elm, vnode.text) } if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) } } function insertBefore (parentNode, newNode, referenceNode) { parentNode.insertBefore(newNode, referenceNode); } /** * * @param vnode根据vnode的数据结构创建真实的dom节点,如果vnode有children则会遍历这些子节点,递归调用createElm方法, * @param insertedVnodeQueue记录子节点创建顺序的队列,每创建一个dom元素就会往队列中插入当前的vnode,当整个vnode对象全部转换成为真实的dom 树时,会依次调用这个队列中vnode hook的insert方法 * @param parentElm * @param refElm * @param nested */ let inPre = 0 function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) { vnode.isRootInsert = !nested // 过渡进入检查 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } const data = vnode.data const children = vnode.children const tag = vnode.tag if (isDef(tag)) { if (process.env.NODE_ENV !== 'production') { if (data && data.pre) { inPre++ } if ( !inPre && !vnode.ns && !( config.ignoredElements.length && config.ignoredElements.some(ignore => { return isRegExp(ignore) ? ignore.test(tag) : ignore === tag }) ) && config.isUnknownElement(tag) ) { warn( 'Unknown custom element: <' + tag + '> - did you ' + 'register the component correctly? For recursive components, ' + 'make sure to provide the "name" option.', vnode.context ) } } vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode) setScope(vnode) /* istanbul ignore if */ if (__WEEX__) { // in Weex, the default insertion order is parent-first. // List items can be optimized to use children-first insertion // with append="tree". const appendAsTree = isDef(data) && isTrue(data.appendAsTree) if (!appendAsTree) { if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } createChildren(vnode, children, insertedVnodeQueue) if (appendAsTree) { if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } } else { createChildren(vnode, children, insertedVnodeQueue) if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } if (process.env.NODE_ENV !== 'production' && data && data.pre) { inPre-- } } else if (isTrue(vnode.isComment)) { vnode.elm = nodeOps.createComment(vnode.text) insert(parentElm, vnode.elm, refElm) } else { vnode.elm = nodeOps.createTextNode(vnode.text) insert(parentElm, vnode.elm, refElm) } } function insert (parent, elm, ref) { if (isDef(parent)) { if (isDef(ref)) { if (ref.parentNode === parent) { nodeOps.insertBefore(parent, elm, ref) } } else { nodeOps.appendChild(parent, elm) } } } function removeVnodes (parentElm, vnodes, startIdx, endIdx) { for (; startIdx <= endIdx; ++startIdx) { const ch = vnodes[startIdx] if (isDef(ch)) { if (isDef(ch.tag)) { removeAndInvokeRemoveHook(ch) invokeDestroyHook(ch) } else { // Text node removeNode(ch.elm) } } } }
aktualisiert hauptsächlich den updateChildren
, indem die untergeordneten Knoten zweier Bäume durch den Schleife und vergleicht die alten, um das Ziel zu erreichen, das Alte mit dem Neuen zu vereinen. while
dom
Lassen Sie es uns anhand eines Beispiels simulieren:
Angenommen, es gibt zwei Bäume, einen alten und einen neuen. Die untergeordneten Knoten im Baum werden durch
dar, wie zum Beispiel: a,b,c,d
vnode
Nach dem Festlegen des Status starten wir den ersten Vergleich. Zu diesem Zeitpunkt trifft
auf die-Logik. und ruft dann direkt auf oldStartVnode=a,newStartVnode=a;
Die Methode aktualisiert den Knoten sameVnode(oldStartVnode,newStartVnode)
und fügt dann 1 zu den Indizes patchVnode(oldStartVnode,newStartVnode,insertedVnodeQueue)
bzw. a
hinzu, wie in der Abbildung gezeigt: oldStartIdx
newStartIdx
Nach der Aktualisierung des Knotens
starten wir den nächsten. Nach zwei Vergleichsdurchgängen wird, wenn auf die a
-Logik trifft, die Methode oldStartVnode=b,newEndVnode=b;
aufgerufen, um den Knoten sameVnode(oldStartVnode,newEndVnode)
zu aktualisieren, und dann patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
wird aufgerufen, um den Knoten b
ganz nach rechts im Baum zu verschieben und schließlich canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
Index +1, b
Index -1, wie in der Abbildung gezeigt: oldStartIdx
newEndIdx
Nach dem Aktualisieren des Knotens
starten wir den dritten Vergleich. Zu diesem Zeitpunkt wird die b
-Logik aufgerufen, um den Knoten oldEndVnode=d,newStartVnode=d;
zu aktualisieren, und dann sameVnode(oldEndVnode, newStartVnode)
wird aufgerufen, um patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
nach links von d
zu verschieben. Schließlich canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
Index -1, d
Index +1, wie in der Abbildung gezeigt: c
oldEndIdx
newStartIdx
Nach der Aktualisierung beginnen wir mit dem vierten Vergleich Diesmal
existiert der Knoten nicht im alten Baum, daher sollte er als neues Element eingefügt werden, rufen Sie d
auf und führen Sie dann die Methode newStartVnode=e
aus, um e
vor createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
einzufügen , und dann nodeOps.insertBefore(parent, elm, ref)
Index +1 hinzufügen, wie in der Abbildung gezeigt: e
c
newStartIdx
Nach dem Einfügen des Knotens können wir sehen, dass
bereits größer ist als, e
Der Zyklus ist abgeschlossen. Rufen Sie dann newStartIdx
auf, um das alte newEndIdx
zu löschen, wie in der letzten Abbildung gezeigt: while
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
c
Die Aktualisierung der alten Baumunterknoten wird durch abgeschlossen Tatsächlich werden nur relativ kleine
-Operationen verwendet, was die Leistung verbessert. Je komplexer die untergeordneten Knoten sind, desto offensichtlicher ist der Verbesserungseffekt.Nachdem updateChildren
über die Methode dom
generiert wurde, wird vnode
aufgerufen. Zu diesem Zeitpunkt wird die gesamte patch
-Instanz erstellt. zwei Rufen Sie die dom
-Methode einmal auf, um ein neues mounted hook
zu generieren, und rufen Sie dann die vue
-Methode auf, um das alte und das neue vue
zu vergleichen, um watcher
zu aktualisieren. Das obige render
vnode
ist das, was ich Für alle zusammengestellt. Ich hoffe, dass es in Zukunft für alle hilfreich sein wird. patch
vnode
Verwandte Artikel: dom
JQuery-Methode für ausgewählte Werte der ausgewählten Komponente
$set und Array in vue.js Method_vue aktualisieren .js
Das obige ist der detaillierte Inhalt vonImplementieren Sie den virtuellen Dom-Patch in Vue (ausführliches Tutorial). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!