Vorwort Die Reaktionsfähigkeit von Vue3 basiert auf Proxy. Verglichen mit der in Vue2 verwendeten Methode Object.definedProperty bietet die Verwendung von Proxy eine gute Unterstützung für das Abfangen neu hinzugefügter Objekte und Arrays. Die Reaktionsfähigkeit von Vue3 ist ein unabhängiges System, das extrahiert und verwendet werden kann. Wie wird sie also erreicht? Wir alle kennen Getter und Setter. Was sind also die wichtigsten Operationen in Getter und Setter, um Reaktionsfähigkeit zu erreichen? Hm, schauen wir uns diese Fragen gemeinsam an. Der Artikel wird Schritt für Schritt ein komplettes responsives System implementieren (falsch)~. Start Die Observer-Util-Bibliothek basiert auf derselben Idee wie Vue3. Die Implementierung in Vue3 ist komplizierter. Beginnen wir mit einer reineren Bibliothek (ich werde das nicht zugeben, weil es in Vue3 einige Dinge gibt, die ich nicht verstehe. Ich werde es nicht tun). Nach dem Beispiel der offiziellen Website: importiere { beobachtbar, beobachte } von '@nx-js/observer-util'; const Zähler = beobachtbar({ num: 0 }); const countLogger = beobachten(() => console.log(Zähler.num)); // dies ruft countLogger auf und protokolliert 1 Zähler.num++; Diese beiden ähneln reaktiv und normal reagierend in Vue3. Dem Objekt nach dem Observable wird ein Proxy hinzugefügt, und die dem Observable hinzugefügte Antwortfunktion wird einmal aufgerufen, wenn sich die abhängige Eigenschaft ändert. Ein kleiner Gedanke Die grobe Idee hier ist ein Abonnement- und Veröffentlichungsmodell. Das Objekt richtet nach der Weiterleitung durch Observable ein Herausgeberlager ein. Observe abonniert zu diesem Zeitpunkt counter.num und ruft dann nacheinander zurück, wenn sich der abonnierte Inhalt ändert. // Listener hinzufügen xxx.addEventListener('counter.num', () => console.log(counter.num)) //Ändere den Inhalt counter.num++ //Benachrichtigung sendenxxx.emit('counter.num', counter.num) Der Kern der Reaktionsfähigkeit besteht darin: Das Hinzufügen von Listenern und Senden von Benachrichtigungen erfolgt automatisch über Observable und Observe. Code-Implementierung Basierend auf den obigen Überlegungen müssen wir in Getter den von „object“ übergebenen Rückruf zum Abonnement-Warehouse hinzufügen. registerRunningReactionForOperation({ Ziel, Schlüssel, Empfänger, Typ: 'get' }) const connectionStore = neue WeakMap() // Reaktionen können sich gegenseitig aufrufen und einen Aufrufstapel bilden const ReaktionsStack = [] // registriere die aktuell laufende Reaktion, um sie bei obj.key-Mutationen erneut in die Warteschlange zu stellen Exportfunktion registerRunningReactionForOperation (Operation) { // aktuelle Reaktion vom oberen Ende des Stapels holen const laufendeReaktion = ReaktionsStack[ReaktionStack.Länge - 1] wenn (laufendeReaktion) { debugOperation(laufende Reaktion, Operation) registerReactionForOperation(laufendeReaktion, Operation) } } Diese Funktion erhält eine Reaktion (d. h. den von „object“ übergebenen Rückruf) und speichert sie über „registerReactionForOperation“. Exportfunktion registerReactionForOperation (Reaktion, { Ziel, Schlüssel, Typ }) { wenn (Typ === 'iterieren') { Schlüssel = ITERATIONSSCHLÜSSEL } const Reaktionen für Obj = connectionStore.get(Ziel) let reactionsForKey = reactionsForObj.get(Schlüssel) wenn (!ReaktionenFürSchlüssel) { Reaktionen für Schlüssel = neuer Satz () ReaktionenFürObjekt.set(Schlüssel, ReaktionenFürSchlüssel) } // Speichern Sie die Tatsache, dass der Schlüssel während des aktuellen Laufs von der Reaktion verwendet wird wenn (!reactionsForKey.has(reaction)) { Reaktionen für Schlüssel.add(Reaktion) Reaktion.Reiniger.Push(Reaktionen für Schlüssel) } } Hier wird ein Set generiert. Entsprechend dem Schlüssel, der im tatsächlichen Geschäft verwendet wird, wird die Reaktion zum Set hinzugefügt. Die gesamte Struktur ist wie folgt: Verbindungsspeicher<schwache Karte>: { // Ziel zB: {num: 1} Ziel: <Karte>{ Num: (Reaktion1, Reaktion2 …) } } Beachten Sie, dass die Reaktion hier const runningReaction = reactionStack[reactionStack.length - 1] über die globale Variable reactionStack abgerufen wird. Exportfunktion beobachten (fn, Optionen = {}) { // die übergebene Funktion in eine Reaktion einbinden, falls dies nicht bereits der Fall ist Konstante Reaktion = fn[IS_REACTION] ? fn : Funktion Reaktion () { returniere runAsReaction(Reaktion, fn, dies, Argumente) } //Speichern Sie den Scheduler und Debugger auf die Reaktion Reaktion.Scheduler = Optionen.Scheduler Reaktion.debugger = Optionen.debugger // Speichern Sie die Tatsache, dass dies eine Reaktion ist Reaktion[IS_REACTION] = wahr // führe die Reaktion einmal aus, wenn es sich nicht um eine verzögerte Reaktion handelt wenn (!optionen.lazy) { Reaktion() } Rücklaufreaktion } Exportfunktion runAsReaction (Reaktion, fn, Kontext, Argumente) { // Bauen Sie keine reaktiven Beziehungen auf, wenn die Reaktion unbeobachtet ist wenn (Reaktion.unbeobachtet) { gibt Reflect.apply(fn, Kontext, Argumente) zurück } // führe die Reaktion nur aus, wenn sie sich nicht bereits im Reaktionsstapel befindet // TODO: Verbessern Sie dies, um explizit rekursive Reaktionen zu ermöglichen wenn (ReaktionsStack.indexOf(Reaktion) === -1) { // die Verbindungen (Objekt -> Schlüssel -> Reaktionen) freigeben // und setze die Cleaner-Verbindungen zurück Freisetzungsreaktion (Reaktion) versuchen { // setze die Reaktion als die aktuell laufende // dies ist erforderlich, damit wir (observable.prop -> Reaktion) Paare in der Get-Trap erstellen können ReaktionStack.push(Reaktion) gibt Reflect.apply(fn, Kontext, Argumente) zurück Endlich // immer das aktuell laufende Flag aus der Reaktion entfernen, wenn die Ausführung beendet wird ReaktionStack.pop() } } } In runAsReaction führt die eingehende Reaktion (das heißt die obige const reaction = function() { runAsReaction(reaction) }) ihre eigene gewrappte Funktion aus, schiebt sie in den Stapel und führt fn aus. Hier ist fn die Funktion, auf die wir automatisch reagieren möchten. Das Ausführen dieser Funktion löst natürlich get aus und diese Reaktion wird in reactionStack vorhanden sein. Beachten Sie hierbei, dass, wenn fn asynchronen Code enthält, die Ausführungsreihenfolge von try schließlich wie folgt lautet: //Führe den Inhalt von try aus, // Wenn eine Rückgabe erfolgt, wird der Rückgabeinhalt ausgeführt, aber es erfolgt keine Rückgabe. Die Rückgabe erfolgt nach der endgültigen Ausführung, und hier tritt keine Blockierung auf. Funktion test() { versuchen { konsole.log(1); const s = () => { console.log(2); return 4; }; gibt s( zurück); Endlich console.log(3) } } // 1 2 3 4 Konsole.log(Test()) Wenn der asynchrone Code also blockiert und vor dem Getter ausgeführt wird, wird die Abhängigkeit nicht erfasst. imitieren Das Ziel besteht darin, Observable und Observe sowie die daraus berechneten Ergebnisse in Vue zu implementieren. Zunächst eine Orientierungskarte: Funktion createObserve(obj) { let-Handler = { get: Funktion (Ziel, Schlüssel, Empfänger) { let result = Reflect.get(Ziel, Schlüssel, Empfänger) Track (Ziel, Schlüssel, Empfänger) Ergebnis zurückgeben }, set: Funktion (Ziel, Schlüssel, Wert, Empfänger) { let result = Reflect.set(Ziel, Schlüssel, Wert, Empfänger) Auslöser (Ziel, Schlüssel, Wert, Empfänger) Ergebnis zurückgeben } } let proxyObj = neuer Proxy(Objekt, Handler) Proxy-Objekt zurückgeben } Funktion beobachtbar(Objekt) { gibt createObserve(obj) zurück. } Hier haben wir nur eine Schicht Proxy-Kapselung erstellt, so wie Vue eine rekursive Kapselung durchführen sollte. Der Unterschied besteht darin, dass bei Verwendung nur einer Kapselungsschicht nur die Operation der äußeren Schicht erkannt werden kann, während die innere Schicht, wie z. B. Array.push oder verschachtelte Ersetzungen, nicht durch Set und Get gelangen kann. Implementierungstrack Im Track werden wir dann den aktuell ausgelösten Effekt, also den Inhalt von „Observer“ oder sonstige Inhalte, in die Beziehungskette schieben, um diesen Effekt bei Auslösung aufrufen zu können. const targetMap = neue WeakMap() let activeEffectStack = [] let aktiver Effekt Funktion Track (Ziel, Schlüssel, Empfänger?) { let depMap = targetMap.get(Ziel) wenn (!depMap) { targetMap.set(Ziel, (depMap = neue Map())) } let dep = depMap.get(Schlüssel) wenn (!dep) { depMap.set(Schlüssel, ( dep = neues Set() )) } wenn (!dep.has(activeEffect)) { dep.add(aktiver Effekt) } } targetMap ist ein weakMap. Der Vorteil der Verwendung von weakMap besteht darin, dass unser beobachtbares Objekt, wenn es keine anderen Referenzen hat, korrekt durch Müll gesammelt wird. Diese Kette ist der zusätzliche Inhalt, den wir erstellt haben, und sie sollte nicht weiter existieren, wenn das ursprüngliche Objekt nicht existiert. Dies wird schließlich Folgendes ergeben: ZielMap = { <Proxy oder Objekt> beobachtbar: <Map>{ <ein Schlüssel im Observable> Schlüssel: (beobachten, beobachten, beobachten…) } } activeEffectStack und activeEffect sind zwei globale Variablen, die für den Datenaustausch verwendet werden. In get fügen wir den aktuellen activeEffect zum durch den get-Schlüssel generierten Set hinzu und speichern ihn, sodass der Set-Vorgang diesen activeEffect abrufen und erneut aufrufen kann, um Reaktionsfähigkeit zu erreichen. Implementieren von TriggernFunktion Trigger(Ziel, Schlüssel, Wert, Empfänger?) { let depMap = targetMap.get(ziel) wenn (!depMap) { zurückkehren } let dep = depMap.get(Schlüssel) wenn (!dep) { zurückkehren } dep.forEach((Artikel) => Artikel && Artikel()) } Der Trigger hier implementiert einen minimalen Inhalt entsprechend der Idee, indem er einfach die in „get“ hinzugefügten Effekte nacheinander aufruft. Implementieren von „observe“ Gemäß der Mindmap müssen wir in „Beobachten“ die übergebene Funktion in „activeEffectStack“ schieben und die Funktion einmal aufrufen, um „get“ auszulösen. Funktion beobachten(fn:Funktion) { const wrapFn = () => { const Reaktion = () => { versuchen { aktiverEffekt = fn activeEffectStack.push(fn) return fn() Endlich activeEffectStack.pop() aktiverEffekt = aktiverEffektStapel[aktiverEffektStapel.Länge - 1] } } Reaktion zurückgeben() } wrapFn() Rückgabewert für WrapFn } Funktion kann einen Fehler machen und der Code in finally stellt sicher, dass der entsprechende in activeEffectStack korrekt gelöscht wird. prüfen sei p = beobachtbar({num: 0}) lass j = beobachte(() => {console.log("ich beobachte:", p.num);) lass e = beobachte(() => {console.log("ich bin beobachte2:", p.num)}) // ich beobachte: 1 // ich bin observe2: 1 p.num++ Implementierung berechneter Eine sehr nützliche Funktion von Vue sind berechnete Eigenschaften. Dabei handelt es sich um neue Werte, die auf Grundlage anderer Eigenschaften generiert werden und sich automatisch ändern, wenn sich die anderen Werte ändern, von denen sie abhängen. Klasse calculatedImpl { privater _Wert privater _setter private Wirkung Konstruktor(Optionen) { this._value = undefiniert this._setter = undefiniert const { get, set } = Optionen this._setter = gesetzt dieser.Effekt = beobachten(() => { dieser._Wert = get() }) } Wert abrufen() { gib diesen Wert zurück } Wert einstellen (val) { dieser._setter && dieser._setter(Wert) } } Funktion berechnet(fnOderOptionen) { let Optionen = { erhalten: null, gesetzt: null } if (fnOrOptions Instanz der Funktion) { options.get = fnOderOptionen } anders { const { get, set } = fnOderOptionen Optionen.get = get Optionen.set = setzen } gibt neues berechnetesImpl(Optionen) zurück } Es gibt zwei Möglichkeiten der Berechnung. Eine ist „computed(function), die als „get“ behandelt wird. Die andere besteht darin, einen Setter festzulegen. Der Setter ist eher wie ein Callback und hat nichts mit anderen abhängigen Eigenschaften zu tun. sei p = beobachtbar({num: 0}) lass j = beobachten(() => {console.log("ich beobachte:", p.num); return `ich beobachte: ${p.num}`}) lass e = beobachte(() => {console.log("ich bin beobachte2:", p.num)}) let w = berechnet(() => { return 'Ich bin berechnet 1:' + p.num }) sei v = berechnet({ erhalten: () => { returniere 'Test des berechneten Getters' + p.num }, setzen: (Wert) => { p.num = `teste berechnete Setter${val}` } }) p.num++ // ich beobachte: 0 // ich bin observe2: 0 // ich beobachte: 1 // ich bin observe2: 1 // Ich werde 1:1 berechnet Konsole.log(w.Wert) v.Wert = 3000 Konsole.log(w.Wert) // ich beobachte: Test berechnet Setter3000 // ich bin observe2: Test berechnet setter3000 // Ich bin berechnet 1:test berechnet setter3000 w.Wert = 1000 // Für w ist kein Setter festgelegt, daher wird es nicht wirksam. // Ich bin berechnet 1:Test berechneter Setter3000 Konsole.log(w.Wert) Dies ist das Ende dieses Artikels zur Implementierung von Vue3 Reactivity. Weitere relevante Inhalte zu Vue3 Reactivity finden Sie in früheren Artikeln auf 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:
|
<<: Detaillierte Schritte zur Installation, Konfiguration und Deinstallation von QT5 in Ubuntu 14.04
>>: Lösung für vergessenes Linux MySQL-Root-Passwort
In diesem Artikelbeispiel wird der spezifische Co...
<br />Das sinnvolle Hinzufügen von Bildern k...
Wirkung Von oben nach unten verblassen Quellcode ...
brauchen Nachdem der Benutzer das Formular ausgef...
Lebenszyklusklassifizierung Jede Komponente von V...
1. Verwendung des Iframe-Tags <br />Wenn es ...
Inhaltsverzeichnis 1. Grundvoraussetzungen für di...
Verwendung von Anker-Tags: Als Ankerlink wird ein ...
Inhaltsverzeichnis Installieren Komponenten impor...
1. Gehen Sie zur offiziellen Website von Vim, um ...
Frontend ist ein harter Job, nicht nur weil sich ...
Heute Wählen Sie * aus Tabellenname, wobei to_day...
Ich benutze den vi-Editor seit mehreren Jahren, h...
Unter Linux ist alles eine Datei, daher besteht d...
Die Syntax für einen äußeren Join lautet wie folg...