Comment comprendre et maîtriser l’essence du DOM virtuel ? Je recommande à tout le monde d'apprendre le projet Snabbdom.
Snabbdom est une bibliothèque d'implémentation de DOM virtuel.Les raisons de la recommandation sont les suivantes : premièrement, le code est relativement petit et le code de base ne fait que quelques centaines de lignes ; les idées de conception/mise en œuvre et d'expansion de ce projet méritent votre référence.
snabb /snab/, suédois, signifie rapide.
Ajustez votre position assise confortable et remontez le moral. Commençons. Pour apprendre le DOM virtuel, nous devons d'abord connaître les connaissances de base du DOM et les problèmes liés à l'utilisation directe du DOM avec JS.
DOM (Document Object Model) est un modèle objet de document qui utilise une structure arborescente d'objets pour représenter un document HTML/XML. La fin de chaque branche de l'arborescence est un nœud contenant des objets. Les méthodes de l'API DOM vous permettent de manipuler cette arborescence de manière spécifique. Avec ces méthodes, vous pouvez modifier la structure, le style ou le contenu du document.
Tous les nœuds de l'arborescence DOM sont d'abord Node
Node
est une classe de base. Element
, Text
et Comment
en héritent tous.
En d’autres termes, Element
, Text
et Comment
sont trois Node
spéciaux, appelés respectivement ELEMENT_NODE
.
TEXT_NODE
et COMMENT_NODE
représentent les nœuds d'éléments (balises HTML), les nœuds de texte et les nœuds de commentaires. Element
a également une sous-classe appelée HTMLElement
. Quelle est la différence entre HTMLElement
et Element
? HTMLElement
représente des éléments en HTML, tels que : <span>
, <img>
, etc., et certains éléments ne sont pas au standard HTML, comme <svg>
. Vous pouvez utiliser la méthode suivante pour déterminer si cet élément est HTMLElement
:
document.getElementById('myIMG') instanceof HTMLElement;
Il est « coûteux » pour le navigateur de créer le DOM. Prenons un exemple classique. Nous pouvons créer un simple élément p via document.createElement('p')
et imprimer tous les attributs :
Vous pouvez voir qu'il y a beaucoup d'attributs imprimés lors de la mise à jour fréquente d'arborescences DOM complexes, des problèmes de performances se produiront. Virtual DOM utilise un objet JS natif pour décrire un nœud DOM, donc la création d'un objet JS est beaucoup moins coûteuse que la création d'un objet DOM.
VNode est une structure d'objet décrivant le DOM virtuel dans Snabbdom. Le contenu est le suivant :
type Key = string number symbol | interface VNœud { // Sélecteur CSS, tel que : 'p#container'. sel : chaîne | non défini ; // Manipulez les classes CSS, les attributs, etc. via des modules. données : VNodeData | non défini ; // Tableau de nœuds enfants virtuels, les éléments du tableau peuvent également être des chaînes. enfants : Array<VNode | chaîne> | // Pointez vers le véritable objet DOM créé. orme : nœud | non défini ; /** * Il existe deux situations pour l'attribut texte : * 1. Le sélecteur sel n'est pas défini, ce qui indique que le nœud lui-même est un nœud de texte. * 2. sel est défini, indiquant que le contenu de ce nœud est un nœud de texte. */ texte : chaîne | non défini ; // Utilisé pour fournir un identifiant pour le DOM existant, qui doit être unique parmi les éléments frères pour éviter efficacement les opérations de reconstruction inutiles. clé : Clé | non définie ; } // Certains paramètres sur vnode.data, les hooks de fonction de classe ou de cycle de vie, etc. interface VNodeData { accessoires?: Accessoires; attributs ? : attributs ; classe ? : Cours ; style ? : VNodeStyle ; ensemble de données ? : ensemble de données ; allumé ? : allumé ; attachData? : AttachData ; crochet?: Crochets; clé?: Clé; ns?: string; // pour les SVG fn?: () => VNode; // pour les mercis args?: any[]; // pour les remerciements is?: string; // pour les éléments personnalisés v1 [key: string]: any; // pour tout autre module tiers }
Par exemple, définissez un objet vnode comme ceci :
const vnode = h( 'p#conteneur', { classe : { actif : vrai } }, [ h('span', { style: { fontWeight: 'bold' } }, 'Ceci est en gras'), 'et c'est juste un texte normal' ]);
Nous créons des objets vnode via la fonction h(sel, b, c)
. L'implémentation du code h()
détermine principalement si les paramètres b et c existent et les traite en données et les enfants finiront par se présenter sous la forme d'un tableau. Enfin, le format de type VNode
défini ci-dessus est renvoyé via la fonction vnode()
.
Prenons d'abord un exemple simple de diagramme du processus en cours d'exécution et ayons d'abord un concept général de processus :
Le traitement différentiel est le processus utilisé pour calculer la différence entre les nouveaux et les anciens nœuds.
Regardons un exemple de code exécuté par Snabbdom :
import { initialisation, module de classe, accessoiresModule, styleModule, eventListenersModule, h, } de 'snabbdom'; const patch = init([ // Initialise la fonction patch classModule en passant le module, // Active la fonction de classes propsModule, // Prise en charge du passage des accessoires styleModule, // Prend en charge les styles en ligne et l'animation eventListenersModule, // Ajoute l'écoute d'événements]); // <p id="conteneur"></p> const conteneur = document.getElementById('conteneur'); const vnode = h( 'p#conteneur.deux.classes', { sur : { cliquez : someFn } }, [ h('span', { style: { fontWeight: 'bold' } }, 'Ceci est en gras'), 'et c'est juste un texte normal', h('a', { props: { href: '/foo' } }, "Je vous emmènerai!"), ] ); // Passe un nœud d'élément vide. patch (conteneur, vnode); const nouveauVnode = h( 'p#conteneur.deux.classes', { sur : { clic : anotherEventHandler } }, [ h( 'portée', { style : { fontWeight : 'normal', fontStyle : 'italic' } }, "C'est maintenant en italique" ), 'et ce n'est encore qu'un texte normal', h('a', { props: { href: ''/bar' } }, "Je vous emmènerai!"), ] ); // Appelez à nouveau patch() pour mettre à jour l'ancien nœud vers le nouveau nœud. patch(vnode, newVnode);
Comme le montrent le diagramme de processus et l'exemple de code, le processus en cours d'exécution de Snabbdom est décrit comme suit :
appelez d'abord init()
pour l'initialisation, et les modules à utiliser doivent être configurés lors de l'initialisation. Par exemple, classModule
permet de configurer l'attribut class des éléments sous forme d'objets ; le module eventListenersModule
permet de configurer les écouteurs d'événements, etc. La fonction patch()
sera renvoyée après l'appel de init()
.
Créez l'objet vnode initialisé via la fonction h()
, appelez la fonction patch()
pour le mettre à jour et enfin créez le véritable objet DOM via createElm()
.
Lorsqu'une mise à jour est requise, créez un nouvel objet vnode, appelez patch()
pour mettre à jour et terminez la mise à jour différentielle de ce nœud et des nœuds enfants via patchVnode()
et updateChildren()
.
Snabbdom utilise la conception de modules pour étendre la mise à jour des propriétés associées au lieu de tout écrire dans le code principal. Alors, comment cela est-il conçu et mis en œuvre ? Venons-en ensuite au contenu principal de la conception de Kangkang, les Hooks : les fonctions de cycle de vie.
Snabbdom fournit une série de fonctions de cycle de vie riches, également appelées fonctions de hook. Ces fonctions de cycle de vie sont applicables dans les modules ou peuvent être définies directement sur vnode. Par exemple, nous pouvons définir l'exécution du hook sur vnode comme ceci :
h('p.row', { clé : 'maLigne', crochet: { insérer : (vnode) => { console.log(vnode.elm.offsetHeight); }, }, });
Toutes les fonctions du cycle de vie sont déclarées comme suit :
nom | paramètres de rappel | du nœud de déclenchement |
---|---|---|
pre | patch démarrage exécution | aucun |
init | vnode est ajouté | vnode |
create | un élément DOM basé sur vnode est créé | emptyVnode, vnode |
insert | vnode est inséré dans le | vnode |
prepatch | vnode est sur le point de patcher | oldVnode, vnode |
update | de vnode a été mis à jour | oldVnode, vnode |
postpatch | de vnode a été corrigé | oldVnode, vnode |
destroy | de vnode a été supprimé directement ou indirectement | vnode |
remove | vnode a supprimé vnode du DOM | vnode, removeCallback |
post | RemoveCallback a terminé le processus de correctif, | aucun |
qui ne s'applique au module : pre
, create
, update
, destroy
, remove
, post
. Les déclarations applicables aux vnode sont : init
, create
, insert
, prepatch
, update
, postpatch
, destroy
, remove
.
Regardons comment Kangkang est implémenté. Par exemple, prenons classModule
comme exemple :
import { VNode, VNodeData } from "../vnode"; importer {Module} depuis "./module" ; type d'exportation Classes = Record<string, boolean> ; function updateClass (oldVnode : VNode, vnode : VNode) : void { // Voici les détails de la mise à jour de l'attribut de classe, ignorez-le pour l'instant. //... } export const classModule: Module = { create: updateClass, update: updateClass };
Vous pouvez voir que la dernière définition du module exporté est un objet. La clé de l'objet est le nom de la Module
hook. comme suit :
importer { pré-crochet, CréerHook, UpdateHook, Détruire le crochet, Supprimer le crochet, PostHook, } de "../hooks" ; type d'export Module = Partiel<{ pré : Pré-Hook ; créer : CreateHook ; mise à jour : UpdateHook ; détruire : détruireHook ; supprimer : SupprimerHook ; poste : PostHook ; }>;
Partial
dans TS signifie que les attributs de chaque clé de l'objet peuvent être vides, c'est-à-dire qu'il suffit de définir le hook qui vous intéresse dans la définition du module. Maintenant que le hook est défini, comment est-il exécuté dans le processus ? Regardons ensuite la fonction init()
:
// Quels sont les hooks qui peuvent être définis dans le module. crochets const : Array<keyof Module> = [ "créer", "mise à jour", "retirer", "détruire", "pré", "poste", ]; fonction d'exportation init( modules : Array<Partial<Module>>, domApi? : DOMAPI, Options ? : Options ) { //La fonction hook définie dans le module sera enfin stockée ici. const cbs : ModuleHooks = { créer: [], mise à jour: [], retirer: [], détruire: [], pré: [], poste: [], } ; //... // Parcourez les hooks définis dans le module et stockez-les ensemble. pour (const crochet de crochets) { pour (const module de modules) { const currentHook = module[hook]; if (currentHook !== non défini) { (cbs[hook] comme any[]).push(currentHook); } } } //... }
Vous pouvez voir que init()
parcourt d'abord chaque module pendant l'exécution, puis stocke la fonction hook dans l'objet cbs
. Lors de l'exécution, vous pouvez utiliser patch()
:
export function init( modules : Array<Partial<Module>>, domApi? : DOMAPI, Options ? : Options ) { //... patch de fonction de retour ( oldVnode : élément VNode | DocumentFragment, nœud virtuel : nœud virtuel ): VNœud { //... // le patch démarre, exécute le pré-hook. pour (i = 0; i < cbs.pre.length; ++i) cbs.pre[i](); //... } }
Ici, nous prenons le pre
-hook comme exemple. Le temps d'exécution du pre
-hook correspond au début de l'exécution du correctif. Vous pouvez voir que patch()
appelle cycliquement les hooks pre
liés stockés dans cbs
au début de l'exécution. Les appels à d'autres fonctions de cycle de vie sont similaires à celui-ci. Vous pouvez voir les appels de fonction de cycle de vie correspondants ailleurs dans le code source.
L'idée de conception ici est le modèle d'observateur . Snabbdom implémente des fonctions non essentielles en les distribuant dans des modules. Combiné avec la définition du cycle de vie, le module peut définir les hooks qui l'intéressent. Ensuite, lorsque init()
est exécuté, il est traité en objets cbs
pour enregistrer ces hooks ; lorsque le moment de l'exécution arrive, appelez. Ces hooks sont utilisés pour notifier le traitement du module. Cela sépare le code principal et le code du module. De là, nous pouvons voir que le modèle d'observateur est un modèle courant pour le découplage de code.
Nous arrivons ensuite à la fonction principale de Kangkang patch()
. Cette fonction est renvoyée après l'appel init()
. Sa fonction est de monter et de mettre à jour le VNode. La signature est la suivante :
function patch(oldVnode: VNode | Element. | DocumentFragment , vnode : VNode) : VNode { // Par souci de simplicité, ne faites pas attention à DocumentFragment. //... }
Le paramètre oldVnode
est l'ancien élément VNode ou DOM ou fragment de document, et le paramètre vnode
est l'objet mis à jour. Ici je poste directement une description du processus :
appeler le pre
hook enregistré sur le module.
Si oldVnode
est Element
, il est converti en un objet vnode
vide et elm
est enregistré dans l'attribut.
Le jugement ici est de savoir s'il s'agit Element
(oldVnode as any).nodeType === 1
est terminé nodeType === 1
indique qu'il s'agit d'un ELEMENT_NODE, qui est défini ici.
Déterminez ensuite si oldVnode
et vnode
sont identiques. sameVnode()
sera appelé ici pour déterminer :
function sameVnode(vnode1: VNode, vnode2: VNode): boolean { //Même clé. const isSameKey = vnode1.key === vnode2.key; // Composant Web, nom de balise d'élément personnalisé, voir ici : // https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement const isSameIs = vnode1.data?.is === vnode2.data?.is; //Même sélecteur. const isSameSel = vnode1.sel === vnode2.sel; // Tous les trois sont identiques. return isSameSel && isSameKey && isSameIs; }
patchVnode()
pour la mise à jour des différences.createElm()
pour créer un nouveau nœud DOM ; après la création, insérez le nœud DOM et supprimez l'ancien nœud DOM.De nouveaux nœuds peuvent être insérés en appelant la file d'attente insert
enregistrée dans l'objet vnode impliqué dans l'opération ci-dessus, patchVnode()
createElm()
. Quant à la raison pour laquelle cela est fait, cela sera mentionné dans createElm()
.
Enfin, le post
hook enregistré sur le module est appelé.
Le processus consiste essentiellement à faire une différence si les nœuds virtuels sont les mêmes, et s'ils sont différents, à en créer de nouveaux et à supprimer les anciens. Voyons ensuite comment createElm()
crée des nœuds DOM.
createElm()
crée un nœud DOM basé sur la configuration de vnode. Le processus est le suivant :
appelez le hook init
qui peut exister sur l'objet vnode.
Ensuite nous traiterons plusieurs situations :
si vnode.sel === '!'
, c'est la méthode utilisée par Snabbdom pour supprimer le nœud d'origine, afin qu'un nouveau nœud de commentaire soit inséré. Étant donné que les anciens nœuds seront supprimés après createElm()
, ce paramètre peut atteindre l'objectif de désinstallation.
Si la définition du sélecteur vnode.sel
existe :
analysez le sélecteur et obtenez id
, tag
et class
.
Appelez document.createElement()
ou document.createElementNS
pour créer un nœud DOM, enregistrez-le dans vnode.elm
et définissez id
, tag
et class
en fonction des résultats de l'étape précédente.
Appelez le hook create
sur le module.
Traitez le tableau children
:
si children
est un tableau, appelez createElm()
de manière récursive pour créer le nœud enfant, puis appelez appendChild
pour le monter sous vnode.elm
.
Si children
n'est pas un tableau mais vnode.text
existe, cela signifie que le contenu de cet élément est du texte. À ce stade, createTextNode
est appelé pour créer un nœud de texte et monté sous vnode.elm
.
Appelez le hook create
sur le vnode. Et ajoutez le hook insert
sur vnode à la file d'attente des hooks insert
.
La situation restante est que vnode.sel
n'existe pas, ce qui indique que le nœud lui-même est du texte, puis appelez createTextNode
pour créer un nœud de texte et enregistrez-le dans vnode.elm
.
Enfin, retournez vnode.elm
.
Il ressort de l'ensemble du processus que createElm()
choisit comment créer des nœuds DOM en fonction de différents paramètres sel
. Il y a un détail à ajouter ici : la file d'attente insert
mentionnée dans patch()
. La raison pour laquelle cette file d'attente de hooks insert
est nécessaire est qu'elle doit attendre que le DOM soit réellement inséré avant de l'exécuter, et elle doit également attendre que tous les nœuds descendants soient insérés, afin que nous puissions calculer les informations de taille et de position du élément dans insert
pour être précis. Combiné avec le processus de création de nœuds enfants ci-dessus, createElm()
est un appel récursif pour créer des nœuds enfants, de sorte que la file d'attente enregistrera d'abord les nœuds enfants, puis elle-même. De cette façon, l'ordre peut être garanti lors de l'exécution de la file d'attente à la fin de patch()
.
Voyons ensuite comment Snabbdom utilise patchVnode()
pour effectuer des comparaisons, qui sont le cœur du DOM virtuel. Le flux de traitement de patchVnode()
est le suivant :
exécutez d’abord le hook prepatch
sur vnode.
Si oldVnode et vnode sont la même référence d'objet, ils seront renvoyés directement sans traitement.
Appelez des hooks update
sur les modules et les vnodes.
Si vnode.text
n'est pas défini, plusieurs cas d' children
sont traités :
si oldVnode.children
et vnode.children
existent tous les deux et ne sont pas identiques. Appelez ensuite updateChildren
pour mettre à jour.
vnode.children
existe mais oldVnode.children
n'existe pas. Si oldVnode.text
existe, effacez-le d'abord, puis appelez addVnodes
pour ajouter un nouveau vnode.children
.
vnode.children
n'existe pas mais oldVnode.children
existe. Appelez removeVnodes
pour supprimer oldVnode.children
.
Si ni oldVnode.children
ni vnode.children
n'existent. Effacez oldVnode.text
s’il existe.
Si vnode.text
est défini et est différent de oldVnode.text
. Si oldVnode.children
existe, appelez removeVnodes
pour l'effacer. Définissez ensuite le contenu du texte via textContent
.
Enfin, exécutez le hook postpatch
sur le vnode.
Le processus montre que les modifications apportées aux attributs associés de ses propres nœuds dans diff, tels que class
, style
, etc., sont mises à jour par le module. Cependant, nous ne développerons pas trop ici si nécessaire. pouvez jeter un oeil au code lié au module. Le traitement de base principal de diff est axé sur children
.Ensuite, Kangkang diff traite plusieurs fonctions liées aux children
.
est très simple. Appelez d'abord createElm()
pour le créer, puis insérez-le dans le parent correspondant.
destory
remove
destory
, ce hook est appelé en premier. La logique est d'appeler d'abord le hook sur l'objet vnode, puis d'appeler le hook sur le module. Ensuite, ce hook est appelé de manière récursive sur vnode.children
dans cet ordre.remove
, ce hook ne sera déclenché que lorsque l'élément actuel est supprimé de son parent. Les éléments enfants de l'élément supprimé ne seront pas déclenchés, et ce hook sera appelé à la fois sur le module et sur l'objet vnode. le module d'abord, puis appelez vnode. Ce qui est plus spécial, c'est que l'élément ne sera pas réellement supprimé tant que toutes remove
ne seront pas appelées. Cela peut répondre à certaines exigences de suppression retardée.Il ressort de ce qui précède que la logique d'appel de ces deux hooks est différente. En particulier, remove
ne sera appelé que sur des éléments directement séparés du parent.
updateChildren()
est utilisé pour traiter les différences de nœuds enfants, et c'est également une fonction relativement complexe dans Snabbdom. L'idée générale est de définir un total de quatre pointeurs de tête et de queue pour oldCh
et newCh
. Ces quatre pointeurs sont respectivement oldStartIdx
, oldEndIdx
, newStartIdx
et newEndIdx
. Comparez ensuite les deux tableaux dans while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx)
pour trouver les mêmes parties à réutiliser et à mettre à jour, et passez à une paire de pointeurs pour chaque comparaison. Le processus de parcours détaillé est traité dans l'ordre suivant :
Si l'un des quatre pointeurs pointe vers vnode == null, alors le pointeur se déplace vers le milieu, par exemple : start++ ou end--, l'occurrence de null sera expliquée plus tard.
Si l'ancien et le nouveau nœuds de démarrage sont identiques, c'est-à-dire que sameVnode(oldStartVnode, newStartVnode)
renvoie true, utilisez patchVnode()
pour effectuer une comparaison, et les deux nœuds de démarrage se déplaceront d'un pas vers le milieu.
Si les anciens et les nouveaux nœuds finaux sont identiques, patchVnode()
est également utilisé et les deux nœuds finaux reculent d'un pas vers le milieu.
Si l'ancien nœud de début est le même que le nouveau nœud de fin, utilisez patchVnode()
pour traiter la mise à jour en premier. Ensuite, le nœud DOM correspondant à oldStart doit être déplacé. La stratégie de déplacement consiste à se déplacer avant le prochain nœud frère du nœud DOM correspondant à oldEndVnode
. Pourquoi ça bouge comme ça ? Tout d'abord, oldStart est identique à newEnd, ce qui signifie que dans le traitement de la boucle en cours, le nœud de départ de l'ancien tableau est déplacé vers la droite car chaque traitement déplace les pointeurs de tête et de queue vers le milieu, nous mettons à jour le pointeur ; ancien tableau vers le nouveau. À ce moment, oldEnd n'a peut-être pas encore été traité, mais à ce moment, oldStart a été déterminé comme étant le dernier du traitement en cours du nouveau tableau, il est donc raisonnable de passer au frère suivant. nœud de oldEnd. Une fois le déplacement terminé, oldStart++ et newEnd-- se déplacent d'un pas au milieu de leurs tableaux respectifs.
Si l'ancien nœud de fin est le même que le nouveau nœud de départ, patchVnode()
est d'abord utilisé pour traiter la mise à jour, puis le nœud DOM correspondant à oldEnd est déplacé vers le nœud DOM correspondant à oldStartVnode
. identique à l'étape précédente. Une fois le déplacement terminé, oldEnd--, newStart++.
Si aucun des cas ci-dessus n'est le cas, utilisez la clé de newStartVnode pour trouver l'indice idx dans oldChildren
. Il existe deux logiques de traitement différentes selon que l'indice existe :
Si l'indice n'existe pas, cela signifie que newStartVnode est nouvellement créé. Créez un nouveau DOM via createElm()
et insérez-le avant le DOM correspondant à oldStartVnode
.
Si l'indice existe, il sera géré dans deux cas :
si le sel des deux vnodes est différent, il sera quand même considéré comme nouvellement créé, créer un nouveau DOM via createElm()
, et l'insérer avant le DOM correspondant à oldStartVnode
.
Si sel est le même, la mise à jour est traitée via patchVnode()
et le vnode correspondant à l'indice de oldChildren
est défini sur undefined. C'est pourquoi == null apparaît dans le parcours précédent à double pointeur. Insérez ensuite le nœud mis à jour dans le DOM correspondant à oldStartVnode
.
Une fois les opérations ci-dessus terminées, newStart++.
Une fois le parcours terminé, il reste encore deux situations à gérer. La première est que oldCh
a été complètement traité, mais il y a encore de nouveaux nœuds dans newCh
, et un nouveau DOM doit être créé pour chaque newCh
restant ; l'autre est que newCh
a été complètement traité et qu'il y a encore d'anciens nœuds dans oldCh
. Les nœuds redondants doivent être supprimés. Les deux situations sont traitées comme suit :
function updateChildren( parentElm : nœud, oldCh : VNode[], nouveauCh : VNode[], inséréVnodeQueue : VNodeQueue ) { // Processus de parcours à double pointeur. //... // Il y a de nouveaux nœuds dans newCh qui doivent être créés. si (newStartIdx <= newEndIdx) { // Doit être inséré avant le dernier newEndIdx traité. avant = newCh[newEndIdx + 1] == null null : newCh[newEndIdx + 1].elm; addVnodes( parentOrme, avant, nouveauCh, nouveauStartIdx, nouveauEndIdx, inséréVnodeQueue ); } // Il y a encore d'anciens nœuds dans oldCh qui doivent être supprimés. si (oldStartIdx <= oldEndIdx) { RemoveVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); } }
Utilisons un exemple pratique pour examiner le processus de traitement de updateChildren()
:
l'état initial est le suivant, l'ancien tableau de nœuds enfants est [A, B, C] et le nouveau tableau de nœuds est [B, A, C , D]:
Lors du premier tour de comparaison, les nœuds de début et de fin sont différents, nous vérifions donc si newStartVnode existe dans l'ancien nœud et trouvons la position de oldCh[1]. Ensuite, exécutons patchVnode()
pour mettre à jour en premier, puis définissons oldCh[1]. ] = undefined et insérez le DOM avant oldStartVnode
, newStartIdx
recule d'un pas et l'état après le traitement est le suivant :
Dans le deuxième tour de comparaison, oldStartVnode
et newStartVnode
sont identiques. Lorsque patchVnode()
est exécuté pour mettre à jour, oldStartIdx
et newStartIdx
se déplacent au milieu après le traitement.
Lors du troisième cycle de comparaison, oldStartVnode == null
, oldStartIdx
se déplace vers le milieu et le statut est mis à jour comme suit :
Dans le quatrième tour de comparaison, oldStartVnode
et newStartVnode
sont identiques. Lorsque patchVnode()
est exécuté pour mettre à jour, oldStartIdx
et newStartIdx
passent au milieu. Après le traitement, l'état est le suivant :
À ce stade, oldStartIdx
est supérieur à oldEndIdx
et la boucle se termine. À l'heure actuelle, il y a encore de nouveaux nœuds qui n'ont pas été traités dans newCh
, et vous devez appeler addVnodes()
pour les insérer. L'état final est le suivant :
, le contenu principal du DOM virtuel a été trié ici. Je pense que les principes de conception et de mise en œuvre de Snabbdom sont très bons. Si vous avez le temps, vous pouvez consulter les détails du code source de Kangkang. les idées valent la peine d’être apprises.