Cette fois, je vais vous apporter une explication détaillée de l'utilisation de l'algorithme diff (avec code). Quelles sont les précautions lors de l'utilisation de l'algorithme diff Voici un cas pratique, jetons un coup d'œil. .
Dom virtuel
L'algorithme de diff doit d'abord clarifier le concept selon lequel l'objet de diff est le dom virtuel, et la mise à jour du dom réel est le résultat de l'algorithme de diff
Classe de base Vnode
constructor ( 。。。 ) { this.tag = tag this.data = data this.children = children this.text = text this.elm = elm this.ns = undefined this.context = context this.fnContext = undefined this.fnOptions = undefined this.fnScopeId = undefined this.key = data && data.key this.componentOptions = componentOptions this.componentInstance = undefined this.parent = undefined this.raw = false this.isStatic = false this.isRootInsert = true this.isComment = false this.isCloned = false this.isOnce = false this.asyncFactory = asyncFactory this.asyncMeta = undefined this.isAsyncPlaceholder = false }
Cette partie du code vise principalement à mieux comprendre la signification de l'attribut du diff spécifique dans l'algorithme diff. Bien sûr, il peut également être modifié. Mieux comprendre l'instance de vnode
Le processus global
La fonction principale est la fonction patch
Jugement isUndef (qu'il soit indéfini ou nul)
// montage vide (probablement en tant que composant), créez un nouvel élément racinecreateElm(vnode, insertVnodeQueue) Ici vous pouvez constater que les nœuds créés ne sont pas insérés un par un, mais mis dans une file d'attente pour unifier le traitement par lots
Fonction principale sameVnode
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) ) ) ) }
Voici une fonction de comparaison externe qui compare directement les clés de deux nœuds, la balise (tag), la comparaison des données (notez que les données ici font référence à VNodeData), en cas d'entrée, comparez directement le type.
export interface VNodeData { key?: string | number; slot?: string; scopedSlots?: { [key: string]: ScopedSlot }; ref?: string; tag?: string; staticClass?: string; class?: any; staticStyle?: { [key: string]: any }; style?: object[] | object; props?: { [key: string]: any }; attrs?: { [key: string]: any }; domProps?: { [key: string]: any }; hook?: { [key: string]: Function }; on?: { [key: string]: Function | Function[] }; nativeOn?: { [key: string]: Function | Function[] }; transition?: object; show?: boolean; inlineTemplate?: { render: Function; staticRenderFns: Function[]; }; directives?: VNodeDirective[]; keepAlive?: boolean; }
Cela confirmera si les deux nœuds méritent une comparaison plus approfondie, sinon ils seront remplacés directement
Le processus de remplacement est principalement une fonction createElm et l'autre est la destruction de oldVNode
// destroy old node if (isDef(parentElm)) { removeVnodes(parentElm, [oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) }
Le processus d'insertion consiste simplement à déterminer le type du nœud et à appeler
createComponent (il déterminera s'il y a des enfants puis l'appellera de manière récursive)
createComment
createTextNode
Après la création, utilisez la fonction d'insertion
Après cela, vous devez utiliser la fonction hydrate pour mapper le dom virtuel et le dom réel
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) } } }
Fonction principale
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 } 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) } }
const el = vnode.el = oldVnode.el C'est une étape très importante Laissez vnode.el se référer au courant. real dom. Lorsque el est modifié, vnode.el changera de manière synchrone.
Comparez si les deux références sont cohérentes
Je ne sais pas ce que fait asyncFactory après ça, donc je ne peux pas comprendre cette comparaison
Clé de comparaison de nœud statique, aucun nouveau rendu ne sera effectué après le même, copiez directement l'instance de composant (une fois que la commande prend effet ici)
Si vnode est un nœud de texte Ou commentaire nœud, mais lorsque vnode.text != oldVnode.text, il vous suffit de mettre à jour le contenu texte de vnode.elm
Comparaison des enfants
Si seulement oldVnode a des nœuds enfants, supprimez ces nœuds
Si seulement vnode a des nœuds enfants, puis créez ces nœuds enfants. Si oldVnode est un nœud de texte, définissez le texte de vnode.elm sur une chaîne vide
Si les deux sont disponibles. , updateChildren sera mis à jour.
Si ni oldVnode ni vnode n'ont de nœuds enfants, mais que oldVnode est un nœud de texte ou un nœud d'annotation, définissez le texte de vnode.elm. à une chaîne vide
updateChildren
L'accent de cette partie est toujours sur l'ensemble de l'algorithme
Quatre premiers pointeurs, oldStart, oldEnd, newStart, newEnd, deux tableaux, oldVnode, Vnode .
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 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (isUndef(oldStartVnode)) { oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left } else if (isUndef(oldEndVnode)) { oldEndVnode = oldCh[--oldEndIdx] } else if (sameVnode(oldStartVnode, newStartVnode)) { patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] } else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) 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)) { // New element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } else { vnodeToMove = oldCh[idxInOld] 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, false, newCh, newStartIdx) } } 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) } }
Plusieurs situations et traitement d'une comparaison de boucle (les ++ suivants -- font tous référence au ++ de l'index --) La comparaison est le nœud à comparer. L'abréviation n'est pas rigoureuse et. la comparaison est utilisée La fonction sameVnode n'est pas vraiment congruente
La condition pour que la boucle entière ne se termine pas oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx
oldStart === newStart, oldStart++ newEnd --
oldEnd === newStart, oldEnd est inséré au début de la file d'attente oldEnd-- newStart++
Toutes les situations restantes sont traitées de cette façon. Pour faire simple, il y a deux processus après le traitement, newStart++
.newStart在old中发现一样的那么将这个移动到oldStart前
没有发现一样的那么创建一个放到oldStart之前
循环结束后并没有完成
还有一段判断才算完
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) }简单的说就是循环结束后,看四个指针中间的内容,old数组中和new数组中,多退少补而已
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!