Warum wird in Vue nicht empfohlen, einen Index als Schlüsselattributwert zu verwenden?

Warum wird in Vue nicht empfohlen, einen Index als Schlüsselattributwert zu verwenden?

Vorwort

Bei der Front-End-Entwicklung wird, solange Listen gerendert werden, unabhängig davon, ob es sich um ein React- oder Vue-Framework handelt, jedes Listenelement aufgefordert oder gefordert, einen eindeutigen Schlüssel zu verwenden. Viele Entwickler verwenden den Array-Index direkt als Schlüsselwert, ohne das Prinzip des Schlüssels zu kennen. In diesem Artikel wird die Rolle des Schlüssels erläutert und erklärt, warum es am besten ist, den Index nicht als Attributwert des Schlüssels zu verwenden.

Die Rolle des Schlüssels

Vue verwendet virtuelles DOM und vergleicht das alte und das neue DOM gemäß dem Diff-Algorithmus, um das reale DOM zu aktualisieren. Der Schlüssel ist die eindeutige Kennung des virtuellen DOM-Objekts. Der Schlüssel spielt im Diff-Algorithmus eine äußerst wichtige Rolle.

Die Rolle des Schlüssels im Diff-Algorithmus

Tatsächlich sind die Diff-Algorithmen in React und Vue ungefähr gleich, aber die Diff-Vergleichsmethoden sind immer noch ziemlich unterschiedlich und sogar die Diffs der einzelnen Versionen sind ziemlich unterschiedlich. Als nächstes nehmen wir den Vue3.0-Diff-Algorithmus als Ausgangspunkt, um die Rolle des Schlüssels im Diff-Algorithmus zu analysieren

Der konkrete Diff-Prozess läuft wie folgt ab

Bild

In Vue3.0 gibt es einen solchen Quellcode in der patchChildren-Methode

wenn (PatchFlag > 0) {
      wenn (patchFlag & PatchFlags.KEYED_FRAGMENT) { 
         /* Für den Fall, dass ein Schlüssel vorhanden ist, verwenden Sie den Diff-Algorithmus */
        patchKeyedChildren(
         ...
        )
        zurückkehren
      } sonst wenn (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
         /* Wenn der Schlüssel nicht existiert, patchen Sie ihn direkt */
        patchUnkeyedChildren( 
          ...
        )
        zurückkehren
      }
    }

patchChildren führt einen echten Diff oder einen direkten Patch aus, je nachdem, ob der Schlüssel vorhanden ist. Wir werden nicht auf den Fall eingehen, dass der Schlüssel nicht existiert.

Sehen wir uns zunächst einige deklarierte Variablen an.

/* c1 alter vnode c2 neuer vnode */
let i = 0 /* Datensatzindex */
const l2 = c2.length /* Anzahl neuer vnodes*/
let e1 = c1.length - 1 /* Index des letzten Knotens des alten vnode*/
let e2 = l2 - 1 /* Der Index des letzten Knotens des neuen Knotens*/

Kopfknoten synchronisieren

Der erste Schritt besteht darin, denselben Vnode vom Anfang an zu finden und ihn dann zu patchen. Wenn es nicht derselbe Knoten ist, springen Sie sofort aus der Schleife heraus.

//(ab) c
//(ab) de
/* Vergleichen Sie von Anfang an, um den gleichen Knoten-Patch zu finden. Wenn er unterschiedlich ist, springen Sie sofort heraus*/
    während (i <= e1 und i <= e2) {
      Konstante n1 = c1[i]
      const n2 = (c2[i] = optimiert
        ? cloneIfMounted(c2[i] als VNode)
        : normalizeVNode(c2[i]))
        /*Überprüfen, ob Schlüssel und Typ gleich sind*/
      wenn (isSameVNodeType(n1, n2)) {
        Patch (
          ...
        )
      } anders {
        brechen
      }
      ich++
    }

Der Ablauf ist wie folgt:

Bild

isSameVNodeType wird verwendet, um zu bestimmen, ob der aktuelle Vnode-Typ dem Vnode-Schlüssel entspricht.

Exportfunktion isSameVNodeType(n1: VNode, n2: VNode): boolean {
  gibt n1.Typ === n2.Typ und n1.Schlüssel === n2.Schlüssel zurück
}

Tatsächlich kennen wir dadurch bereits die Rolle des Schlüssels im Diff-Algorithmus, nämlich die Bestimmung, ob es sich um denselben Knoten handelt.

Endknoten synchronisieren

Der zweite Schritt beginnt am Ende mit dem gleichen Diff wie zuvor.

//ein (bc)
//de (bc)
/* Wenn der erste Schritt nicht gepatcht ist, beginnen Sie sofort mit dem Patchen von hinten nach vorne. Wenn Sie Unterschiede feststellen, springen Sie sofort aus der Schleife heraus*/
    während (i <= e1 und i <= e2) {
      Konstante n1 = c1[e1]
      const n2 = (c2[e2] = optimiert
        ? cloneIfMounted(c2[e2] als VNode)
        : normalizeVNode(c2[e2]))
      wenn (isSameVNodeType(n1, n2)) {
        Patch (
         ...
        )
      } anders {
        brechen
      }
      e1--
      e2--
    }

Wenn nach dem ersten Schritt festgestellt wird, dass der Patch nicht vollständig ist, fahren Sie sofort mit dem zweiten Schritt fort. Beginnen Sie dabei am Ende und durchlaufen Sie den Vorwärts-Diff. Wenn es nicht derselbe Knoten ist, dann sofort aus der Schleife springen. Der Ablauf ist wie folgt:

Einen neuen Knoten hinzufügen

Schritt 3: Wenn alle alten Knoten gepatcht sind und die neuen Knoten nicht gepatcht sind, erstellen Sie einen neuen Vnode

//(ab)
//(ab) c
//i = 2, e1 = 1, e2 = 2
//(ab)
//c (ab)
//i = 0, e1 = -1, e2 = 0
/* Wenn die Anzahl der neuen Knoten größer ist als die Anzahl der alten Knoten, werden alle verbleibenden Knoten als neue Vnodes verarbeitet (das bedeutet, dass derselbe Vnode gepatcht wurde) */
    wenn (i > e1) {
      wenn (i <= e2) {
        const nextPos = e2 + 1
        const anchor = nächstePos < l2 ? (c2[nextPos] als VNode).el : parentAnchor
        während (i <= e2) {
          patch (/* einen neuen Knoten erstellen */
            ...
          )
          ich++
        }
      }
    }

Der Ablauf ist wie folgt:

Bild

Löschen redundanter Knoten

Schritt 4: Wenn alle neuen Knoten gepatcht sind und noch einige alte Knoten übrig sind, deinstallieren Sie alle alten Knoten.

//ich > e2
//(ab) c
//(ab)
//i = 2, e1 = 2, e2 = 1
//ein (bc)
//(bc)
//i = 0, e1 = 0, e2 = -1
sonst wenn (i > e2) {
   während (i <= e1) {
      aushängen(c1[i], übergeordnete Komponente, übergeordnete Spannung, wahr)
      ich++
   }
}

Der Ablauf ist wie folgt:

Längste zunehmende Teilfolge

Zu diesem Zeitpunkt ist die Kernszene noch nicht erschienen. Wenn wir Glück haben, könnte es hier enden, aber wir können uns nicht ausschließlich auf Glück verlassen. Das verbleibende Szenario ist, dass sowohl der neue als auch der alte Knoten mehrere untergeordnete Knoten haben. Sehen wir uns als Nächstes an, wie Vue3 das macht. So kombinieren Sie Verschiebe-, Hinzufügungs- und Deinstallationsvorgänge

Jedes Mal, wenn wir ein Element verschieben, können wir ein Muster erkennen. Wenn wir es so selten wie möglich verschieben möchten, bedeutet dies, dass einige Elemente stabil sein müssen. Was sind also die Muster für Elemente, die stabil bleiben können?

Schauen Sie sich das obige Beispiel an: c h d e VS d e i c. Beim Vergleichen können Sie mit bloßem Auge erkennen, dass Sie c nur ans Ende verschieben, dann h deinstallieren und i hinzufügen müssen. d e kann unverändert bleiben. Es zeigt sich, dass die Reihenfolge von d e in den neuen und alten Knoten unverändert bleibt. d kommt nach e und der Index ist in einem zunehmenden Zustand.

Hier führen wir ein Konzept ein, das als längste zunehmende Teilfolge bezeichnet wird.
Offizielle Erklärung: Finden Sie in einem gegebenen Array eine Menge ansteigender Werte mit der größtmöglichen Länge.
Das ist etwas schwer zu verstehen, schauen wir uns also ein konkretes Beispiel an:

const arr = [10, 9, 2, 5, 3, 7, 101, 18]
=> [2, 3, 7, 18]
Dieses Array ist die am längsten zunehmende Teilfolge von arr, ebenso wie [2, 3, 7, 101].
Die am längsten zunehmende Teilfolge erfüllt also drei Anforderungen:
1. Die Werte in der Teilsequenz nehmen zu
2. Die Indizes der Werte in der Teilsequenz nehmen im ursprünglichen Array zu
3. Diese Teilfolge ist die längste, die gefunden werden kann, aber normalerweise finden wir die Menge der Folgen mit kleineren Werten, da diese mehr Raum zum Wachsen haben.

Die nächste Idee ist: Wenn wir die Knoten finden, deren Reihenfolge in der neuen Knotensequenz unverändert bleibt, wissen wir, welche Knoten nicht verschoben werden müssen, und müssen dann nur die Knoten einfügen, die nicht hier sind. Denn die endgültige Reihenfolge, die dargestellt werden soll, ist die Reihenfolge der neuen Knoten, und die Bewegung ist lediglich die Bewegung der alten Knoten. Solange die alten Knoten also die längste Reihenfolge unverändert beibehalten, kann die Konsistenz damit durch Verschieben einzelner Knoten gewahrt werden. Suchen Sie also vorher zuerst alle Knoten und dann die entsprechende Sequenz. Was wir am Ende eigentlich erhalten wollen, ist dieses Array: [2, 3, neu, 0]. Tatsächlich ist dies die Idee der Diff-Bewegung

Bild

Warum keinen Index verwenden?

Leistungskosten

Bei Verwendung eines Index als Schlüssel wird der sequentielle Vorgang zerstört, da nicht jeder Knoten den entsprechenden Schlüssel finden kann, einige Knoten nicht wiederverwendet werden können und alle neuen Vnodes neu erstellt werden müssen.

Beispiel:

<Vorlage>
  <div Klasse="hallo">
    <ul>
      <li v-for="(item,index) in studentList" :key="index">{{item.name}}</li>
      <br>
      <button @click="addStudent">Datenelement hinzufügen</button>
    </ul>
 
  </div>
</Vorlage>
 
<Skript>
Standard exportieren {
  Name: "Hallo Welt",
  Daten() {
    zurückkehren {
      Studentenliste: [
        { ID: 1, Name: '张三', Alter: 18 },
        { ID: 2, Name: 'Li Si', Alter: 19 },
      ],
    };
  },
  Methoden:{
    addStudent(){
      const studentObj = { id: 3, name: 'student', age: 20 };
      diese.studentenliste=[studentenobj,...diese.studentenliste]
    }
  }
}
</Skript>

Öffnen wir zuerst den Chrome-Debugger und doppelklicken Sie dann, um den darin enthaltenen Text zu ändern.

Bild

Lassen Sie uns den obigen Code ausführen und die Ergebnisse ansehen.

Bild

Aus den obigen Ergebnissen können wir ersehen, dass wir nur ein Datenelement hinzugefügt haben, aber alle drei Datenelemente neu gerendert werden müssen. Ist das nicht überraschend? Ich habe offensichtlich nur ein Datenelement eingefügt, warum müssen alle drei Datenelemente neu gerendert werden? Und ich möchte lediglich die neu hinzugefügten Daten rendern.

Wir haben oben auch über die Diff-Vergleichsmethode gesprochen. Lassen Sie uns nun ein Bild basierend auf dem Diff-Vergleich zeichnen, um zu sehen, wie der Vergleich durchgeführt wird.

Bild

Wenn wir vorne ein Datenelement hinzufügen, wird die Indexreihenfolge unterbrochen, wodurch sich alle Schlüssel der neuen Knoten ändern und die Daten auf unserer Seite neu gerendert werden.

Als nächstes generieren wir 1000 DOMs, um die Leistung bei Verwendung eines Index und bei Nichtverwendung eines Index zu vergleichen. Um die Eindeutigkeit des Schlüssels sicherzustellen, verwenden wir UUID als Schlüssel.

Verwenden wir den Index als Schlüssel und führen ihn einmal aus

<Vorlage>
  <div Klasse="hallo">
    <ul>
      <button @click="addStudent">Datenelement hinzufügen</button>
      <br>
      <li v-for="(item,index) in studentList" :key="index">{{item.id}}</li>
    </ul>
  </div>
</Vorlage>
 
<Skript>
importiere uuidv1 von „uuid/v1“
Standard exportieren {
  Name: "Hallo Welt",
  Daten() {
    zurückkehren {
      Studentenliste: [{id:uuidv1()}],
    };
  },
  erstellt(){
    für (sei i = 0; i < 1000; i++) {
      diese.studentList.push({
        Ich würde sagen: uuidv1(),
      });
    }
  },
  vorUpdate(){
    Konsole.Zeit('für');
  },
  aktualisiert(){
    console.timeEnd('für') //für: 75,259033203125 ms
  },
  Methoden:{
    addStudent(){
      const studentObj = { id: uuidv1() };
      diese.studentenliste=[studentenobj,...diese.studentenliste]
    }
  }
}
</Skript>

Ersetzen Sie die ID durch den Schlüssel

<Vorlage>
  <div Klasse="hallo">
    <ul>
      <button @click="addStudent">Datenelement hinzufügen</button>
      <br>
      <li v-for="(item,index) in studentList" :key="item.id">{{item.id}}</li>
    </ul>
  </div>
</Vorlage>
  vorUpdate(){
    Konsole.Zeit('für');
  },
  aktualisiert(){
    console.timeEnd('für') //für: 42,200927734375 ms
  },

Aus dem obigen Vergleich können wir erkennen, dass die Verwendung eines eindeutigen Wertes als Schlüssel Mehraufwand sparen kann

Datenfehlausrichtung

Im obigen Beispiel denken Sie vielleicht, dass die Verwendung des Index als Schlüssel nur die Effizienz des Seitenladens beeinflusst, und dass eine kleine Datenmenge kaum Auswirkungen hat. Im folgenden Fall kann die Verwendung des Index einige unerwartete Probleme verursachen. Im obigen Szenario füge ich immer noch zuerst nach jedem Textinhalt ein Eingabefeld hinzu, fülle manuell etwas Inhalt in das Eingabefeld aus und verwende dann die Schaltfläche, um einen Klassenkameraden weiterzuhängen.

<Vorlage>
  <div Klasse="hallo">
    <ul>
      <li v-for="(item,index) in studentList" :key="index">{{item.name}}<input /></li>
      <br>
      <button @click="addStudent">Datenelement hinzufügen</button>
    </ul>
  </div>
</Vorlage>
 
<Skript>
Standard exportieren {
  Name: "Hallo Welt",
  Daten() {
    zurückkehren {
      Studentenliste: [
        { ID: 1, Name: '张三', Alter: 18 },
        { ID: 2, Name: 'Li Si', Alter: 19 },
      ],
    };
  },
  Methoden:{
    addStudent(){
      const studentObj = { id: 3, name: 'student', age: 20 };
      diese.studentenliste=[studentenobj,...diese.studentenliste]
    }
  }
}
</Skript>

Geben wir einige Werte in die Eingabe ein und fügen einen Klassenkameraden hinzu, um den Effekt zu sehen:

Bild

Zu diesem Zeitpunkt stellen wir fest, dass die vor dem Hinzufügen eingegebenen Daten falsch platziert sind. Nach dem Hinzufügen verbleiben die Informationen von Zhang San im Eingabefeld von Wang Wu, was offensichtlich nicht das gewünschte Ergebnis ist.

Bild

Aus dem obigen Vergleich können wir ersehen, dass beim Vergleichen festgestellt wird, dass sich der Textwert zwar geändert hat, da der Index als Schlüssel verwendet wird. Beim weiteren Abwärtsvergleich stellt sich jedoch heraus, dass der Eingabe-DOM-Knoten immer noch derselbe ist wie zuvor und daher wiederverwendet wird. Es ist jedoch unerwartet, dass das Eingabefeld den Eingabewert beibehält, und zu diesem Zeitpunkt wird der Eingabewert falsch platziert.

Lösung

Da wir wissen, dass die Verwendung eines Index in einigen Fällen sehr negative Auswirkungen haben kann, stellt sich die Frage, wie wir dieses Problem während der Entwicklung lösen können. Tatsächlich werden die folgenden drei Situationen bei der Entwicklung im Allgemeinen häufiger verwendet, solange der Schlüssel eindeutig und unverändert ist.

  • Bei der Entwicklung empfiehlt es sich, für jedes Datenelement eine eindeutige Kennung als Schlüssel zu verwenden, z. B. einen eindeutigen Wert, der vom Hintergrund zurückgegeben wird, z. B. eine ID, eine Mobiltelefonnummer, eine Ausweisnummer usw.
  • Sie können Symbol als Schlüssel verwenden. Symbol ist ein neuer primitiver Datentyp, der in ES6 eingeführt wurde. Es stellt einen eindeutigen Wert dar und wird hauptsächlich verwendet, um den eindeutigen Eigenschaftsnamen eines Objekts zu definieren.
lass a=Symbol('test')
lass b = Symbol('Test')
console.log(a===b) //false

Als Schlüssel können Sie uuid verwenden. uuid ist die Abkürzung für Universally Unique Identifier. Es handelt sich dabei um eine maschinengenerierte Kennung, die innerhalb eines bestimmten Bereichs (von einem bestimmten Namensraum bis hin zur ganzen Welt) eindeutig ist.

Wir verwenden die erste Lösung oben als Schlüssel und betrachten die obige Situation noch einmal, wie in der Abbildung dargestellt. Knoten mit demselben Schlüssel werden wiederverwendet. Dies ist die eigentliche Wirkung des Diff-Algorithmus.

Bild

Bild

Bild

Zusammenfassen

  • Bei Verwendung des Index als Schlüssel werden beim Hinzufügen oder Löschen der Daten in umgekehrter Reihenfolge unnötige echte DOM-Updates generiert, was zu einer geringen Effizienz führt.
  • Wenn Sie den Index als Schlüssel verwenden und die Struktur DOM der Eingabeklasse enthält, kommt es zu falschen DOM-Updates
  • Bei der Entwicklung empfiehlt es sich, für jedes Datenelement eine eindeutige Kennung als Schlüssel zu verwenden, z. B. einen eindeutigen Wert, der vom Hintergrund zurückgegeben wird, z. B. eine ID, eine Mobiltelefonnummer, eine Ausweisnummer usw.
  • Wenn keine Operation stattfindet, die die Reihenfolge zerstört, wie z. B. das Hinzufügen oder Löschen von Daten in umgekehrter Reihenfolge, und sie nur zum Rendern und Anzeigen verwendet wird, ist es auch möglich, den Index als Schlüssel zu verwenden (es wird jedoch trotzdem nicht empfohlen, gute Entwicklungsgewohnheiten zu entwickeln).

Damit ist dieser Artikel darüber, warum es nicht empfohlen wird, in Vue einen Index als Schlüssel zu verwenden, abgeschlossen. Weitere relevante Inhalte zum Vue-Index als Schlüssel finden Sie in den vorherigen Artikeln von 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

Das könnte Sie auch interessieren:
  • Warum wird in Vue nicht empfohlen, einen Index als Schlüsselattributwert zu verwenden?
  • Warum kann ich bei Verwendung von v-for in Vue keinen Index als Schlüssel verwenden?
  • Detaillierte Erklärung, warum der Index in Vue nicht als Schlüssel verwendet werden sollte (Diff-Algorithmus)
  • Eine kurze Diskussion über die Änderungen in der v-for-Iterationssyntax in Vue 2.0 (Schlüssel, Index)
  • Einige Dinge wurden in vue2.0 entfernt oder geändert (Indexschlüssel entfernt)

<<:  Designtheorie: Menschenorientiertes Designkonzept

>>:  CSS-Leistungsoptimierung - detaillierte Erklärung der Will-Change-Verwendung

Artikel empfehlen

Teilen Sie 10 der neuesten Web-Frontend-Frameworks (Übersetzung)

In der Welt der Webentwicklung sind Frameworks wei...

So fragen Sie doppelte Daten in einer MySQL-Tabelle ab

INSERT INTO hk_test(Benutzername, Passwort) VALUE...

SVN-Installation und grundlegende Bedienung (grafisches Tutorial)

Inhaltsverzeichnis 1. Was ist SVN? 2. Methoden zu...

Bedingtes Rendering von Vue (v-if und v-show)

Inhaltsverzeichnis 1. v-wenn 2. Verwenden Sie v-i...

Semantisierung von HTML-Tags (einschließlich H5)

einführen HTML stellt die kontextuelle Struktur u...

Docker löscht private Bibliotheksbilder vollständig

Werfen wir zunächst einen Blick auf die allgemein...

Detaillierte Erklärung zur korrekten Installation von OpenCV auf Ubuntu

Dieser Artikel beschreibt, wie man OpenCV mit C++...

Geben Sie einige Tipps zur Verwendung von JavaScript-Operatoren

Inhaltsverzeichnis 1. Optionaler Verkettungsopera...

Ist das Tag „li“ ein Blockelement?

Warum kann es die Höhe festlegen, aber im Gegensat...

Implementierung der Nginx-Konfiguration HTTPS-Sicherheitsauthentifizierung

1. Der Unterschied zwischen HTTP und HTTPS HTTP: ...

Detaillierte Erklärung der MySQL-Berechtigungssteuerung

Inhaltsverzeichnis MySQL-Berechtigungskontrolle B...