Erfahren Sie, wie Sie Vue3 Reactivity implementieren

Erfahren Sie, wie Sie Vue3 Reactivity implementieren

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.
Pseudocode:

// 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.
In der konkreten Implementierung fügt Observable einen Handler für das beobachtete Objekt hinzu. Im Getter-Handler gibt es einen

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.
Wenn wir die Idee von Vue3 übernehmen, wird der Vorgang beim Abrufen als „Track“ bezeichnet, der Vorgang beim Festlegen als „Trigger“ und der Rückruf als „Effect“.

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 Triggern

Funktion 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.
Nachdem wir Ovserve implementiert hatten, war Computed fast zur Hälfte implementiert.

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:
  • Einführung in die reaktive Funktion toRef-Funktion ref-Funktion in Vue3
  • Details zum Schreiben von React in einem Vue-Projekt
  • Detaillierte Analyse des Unterschieds zwischen Ref und Reactive in Vue3.0
  • Detaillierte Erklärung und Erweiterung von ref und reactive in Vue3
  • Detaillierte Erklärung der Verwendung von SetUp- und Reactive-Funktionen in Vue3
  • Die vollständige Verwendung von Setup, Ref und Reactive in der Vue3-Kombinations-API
  • Detaillierte Erklärung der drei wichtigsten Frontend-Technologien React, Angular und Vue
  • Unterschiede und Vorteile von Vue und React
  • Was sind die Unterschiede zwischen Vue und React?
  • Vue und React im Detail

<<:  Detaillierte Schritte zur Installation, Konfiguration und Deinstallation von QT5 in Ubuntu 14.04

>>:  Lösung für vergessenes Linux MySQL-Root-Passwort

Artikel empfehlen

Vue erzielt einen nahtlosen Karusselleffekt (Laufschrift)

In diesem Artikelbeispiel wird der spezifische Co...

XHTML-Tutorial für die ersten Schritte: XHTML-Webseiten-Bildanwendung

<br />Das sinnvolle Hinzufügen von Bildern k...

Detaillierte Erklärung des Unterschieds zwischen Vue-Lebenszyklus

Lebenszyklusklassifizierung Jede Komponente von V...

Analyse der Unterschiede zwischen Iframe und FRAME

1. Verwendung des Iframe-Tags <br />Wenn es ...

MySQL-Datenbank-JDBC-Programmierung (Java stellt eine Verbindung zu MySQL her)

Inhaltsverzeichnis 1. Grundvoraussetzungen für di...

Beispiel für die gemeinsame Nutzung von Anker-Tags in HTML

Verwendung von Anker-Tags: Als Ankerlink wird ein ...

So verwenden Sie die Markdown-Editor-Komponente in Vue3

Inhaltsverzeichnis Installieren Komponenten impor...

So fügen Sie Codebeispiele für die Vim-Implementierung in Power Shell hinzu

1. Gehen Sie zur offiziellen Website von Vim, um ...

4 Lösungen für CSS-Browserkompatibilitätsprobleme

Frontend ist ein harter Job, nicht nur weil sich ...

Zusammenfassung der neuen Verwendung von vi (vim) unter Linux

Ich benutze den vi-Editor seit mehreren Jahren, h...

So verwenden Sie das Schreiben von Dateien zum Debuggen einer Linux-Anwendung

Unter Linux ist alles eine Datei, daher besteht d...

Der Unterschied zwischen MySQL Outer Join und Inner Join Abfrage

Die Syntax für einen äußeren Join lautet wie folg...