Heim > Web-Frontend > js-Tutorial > WTF ist Reaktivität!?

WTF ist Reaktivität!?

DDD
Freigeben: 2024-12-22 05:44:10
Original
343 Leute haben es durchsucht

Reaktivitätsmodelle erklärt

Vorwort

Es ist (schon) 10 Jahre her, seit ich mit der Entwicklung von Anwendungen und Websites begonnen habe, aber das JavaScript-Ökosystem war noch nie so spannend wie heute!

Im Jahr 2022 war die Community vom Konzept „Signal“ so fasziniert, dass die meisten JavaScript-Frameworks sie in ihre eigene Engine integrierten. Ich denke an Preact, das seit September 2022 vom Komponentenlebenszyklus entkoppelte reaktive Variablen anbietet; oder in jüngerer Zeit Angular, das Signale im Mai 2023 experimentell implementierte, dann offiziell ab Version 18. Auch andere JavaScript-Bibliotheken haben beschlossen, ihren Ansatz zu überdenken...

Von 2023 bis heute habe ich Signale konsequent in verschiedenen Projekten verwendet. Ihre einfache Implementierung und Nutzung hat mich voll und ganz überzeugt, sodass ich ihre Vorteile in technischen Workshops, Schulungen und Konferenzen mit meinem beruflichen Netzwerk geteilt habe.

Aber in letzter Zeit begann ich mich zu fragen, ob dieses Konzept wirklich „revolutionär“ war/ob es Alternativen zu Signals gibt? Also habe ich mich eingehender mit dieser Überlegung befasst und verschiedene Ansätze für reaktive Systeme entdeckt.

Dieser Beitrag gibt einen Überblick über verschiedene Reaktivitätsmodelle und mein Verständnis ihrer Funktionsweise.

Hinweis: An dieser Stelle, Sie haben es wahrscheinlich erraten, werde ich nicht über Javas „Reaktive Streams“ sprechen; andernfalls hätte ich diesen Beitrag mit „WTF ist Gegendruck!?“ betitelt. ?

Theorie

Wenn wir über Reaktivitätsmodelle sprechen, sprechen wir (in erster Linie) von „reaktiver Programmierung“, insbesondere aber von „Reaktivität“.

Die reaktive Programmierung ist ein Entwicklungsparadigma, das es ermöglicht, die Änderung einer Datenquelle automatisch an Verbraucher weiterzugeben.

Also können wir die Reaktivität als die Fähigkeit definieren, Abhängigkeiten in Echtzeit abhängig von der Datenänderung zu aktualisieren.

Hinweis: Kurz gesagt, wenn ein Benutzer ein Formular ausfüllt und/oder absendet, müssen wir auf diese Änderungen reagieren, eine Ladekomponente oder irgendetwas anderes anzeigen, das angibt, dass etwas passiert. .. Ein weiteres Beispiel: Beim asynchronen Empfang von Daten müssen wir reagieren, indem wir alle oder einen Teil dieser Daten anzeigen, eine neue Aktion ausführen usw.

In diesem Zusammenhang stellen reaktive Bibliotheken Variablen bereit, die automatisch aktualisiert und effizient weitergegeben werden, wodurch es einfacher wird, einfachen und optimierten Code zu schreiben.

Um effizient zu sein, müssen diese Systeme diese Variablen genau dann neu berechnen/auswerten, wenn sich ihre Werte geändert haben! Um sicherzustellen, dass die übertragenen Daten konsistent und aktuell bleiben, muss das System die Anzeige von Zwischenzuständen vermeiden (insbesondere während der Berechnung von Zustandsänderungen).

Hinweis: Der Status bezieht sich auf die Daten/Werte, die während der gesamten Lebensdauer eines Programms/einer Anwendung verwendet werden.

Okay, aber dann... Was genau sind diese „Reaktivitätsmodelle“?

PUSH, auch bekannt als „Eager“ Reaktivität

Das erste Reaktivitätsmodell heißt „PUSH“ (oder „eifrige“ Reaktivität). Dieses System basiert auf den folgenden Prinzipien:

  • Initialisierung von Datenquellen (bekannt als „Observables“)
  • Komponenten/Funktionen abonnieren diese Datenquellen (das sind die Verbraucher)
  • Wenn sich ein Wert ändert, werden die Daten sofort an die Verbraucher (sogenannte „Beobachter“) weitergegeben

Wie Sie vielleicht schon erraten haben, basiert das „PUSH“-Modell auf dem Designmuster „Observable/Observer“.

1. Anwendungsfall: Anfangszustand und Zustandsänderung

Betrachten wir den folgenden Ausgangszustand

let a = { firstName: "John", lastName: "Doe" };
const b = a.firstName;
const c = a.lastName;
const d = `${b} ${c}`;
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

WTF Is Reactivity !?

Bei Verwendung einer reaktiven Bibliothek (wie RxJS) würde dieser Anfangszustand eher so aussehen:

let a = observable.of({ firstName: "John", lastName: "Doe" });
const b = a.pipe(map((a) => a.firstName));
const c = a.pipe(map((a) => a.lastName));
const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Hinweis: Für diesen Beitrag sollten alle Codeschnipsel als „Pseudocode“ betrachtet werden.

Nehmen wir nun an, dass ein Verbraucher (zum Beispiel eine Komponente) den Wert von Zustand D protokollieren möchte, wann immer diese Datenquelle aktualisiert wird,

d.subscribe((value) => console.log(value));
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Unsere Komponente würde den Datenstrom abonnieren; es muss noch eine Veränderung auslösen,

a.next({ firstName: "Jane", lastName: "Doe" });
Nach dem Login kopieren
Nach dem Login kopieren

Von dort aus erkennt das „PUSH“-System die Änderung und sendet sie automatisch an die Verbraucher. Basierend auf dem oben genannten Ausgangszustand finden Sie hier eine Beschreibung der Vorgänge, die auftreten können:

  • Zustandsänderung tritt in Datenquelle A auf!
  • Der Wert von A wird an B weitergegeben (Berechnung der Datenquelle B);
  • Dann wird der Wert von B an D weitergegeben (Berechnung der Datenquelle D);
  • Der Wert von A wird an C weitergegeben (Berechnung der Datenquelle C);
  • Schließlich wird der Wert von C an D weitergegeben (Neuberechnung der Datenquelle D);

WTF Is Reactivity !?

Eine der Herausforderungen dieses Systems liegt in der Reihenfolge der Berechnungen. Basierend auf unserem Anwendungsfall werden Sie tatsächlich feststellen, dass D möglicherweise zweimal ausgewertet wird: ein erstes Mal mit dem Wert von C in seinem vorherigen Zustand; und ein zweites Mal mit dem aktuellen C-Wert! In dieser Art von Reaktivitätsmodell wird diese Herausforderung als „Diamantproblem“ ♦️ bezeichnet.

2. Anwendungsfall: Nächste Iteration

Nehmen wir nun an, dass sich der Staat auf zwei Hauptdatenquellen verlässt,

let a = { firstName: "John", lastName: "Doe" };
const b = a.firstName;
const c = a.lastName;
const d = `${b} ${c}`;
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Beim Aktualisieren von E berechnet das System den gesamten Status neu, wodurch eine einzige Quelle der Wahrheit erhalten bleibt, indem der vorherige Status überschrieben wird.

  • Zustandsänderung tritt in Datenquelle E auf!
  • Der Wert von A wird an B weitergegeben (Berechnung der Datenquelle B);
  • Dann wird der Wert von B an D weitergegeben (Berechnung der Datenquelle D);
  • Der Wert von A wird an C weitergegeben (Berechnung der Datenquelle C);
  • Der Wert von E wird an C weitergegeben (Neuberechnung der Datenquelle C);.
  • Schließlich wird der Wert von C an D weitergegeben (Neuberechnung der Datenquelle D);

WTF Is Reactivity !?

Wieder einmal tritt das „Diamantproblem“ auf... Dieses Mal auf der Datenquelle C, die möglicherweise zweimal ausgewertet wird, und zwar immer auf D.

Diamantproblem

Das „Diamantenproblem“ ist keine neue Herausforderung im „eifrigen“ Reaktivitätsmodell. Einige Berechnungsalgorithmen (insbesondere die von MobX verwendeten) können die „Knoten des reaktiven Abhängigkeitsbaums“ markieren, um die Zustandsberechnung auszugleichen. Bei diesem Ansatz würde das System zuerst die „Root“-Datenquellen (A und E in unserem Beispiel), dann B und C und schließlich D auswerten. Eine Änderung der Reihenfolge der Zustandsberechnungen hilft, diese Art von Problem zu beheben.

WTF Is Reactivity !?

PULL, auch bekannt als „faule“ Reaktivität

Das zweite Reaktivitätsmodell heißt "PULL". Im Gegensatz zum „PUSH“-Modell basiert es auf den folgenden Prinzipien:

  • Deklaration reaktiver Variablen
  • Das System verschiebt die Zustandsberechnung
  • Der abgeleitete Status wird basierend auf seinen Abhängigkeiten berechnet
  • Das System vermeidet übermäßige Aktualisierungen

Diese letzte Regel ist am wichtigsten, die Sie sich merken sollten: Im Gegensatz zum vorherigen System verschiebt diese letzte Regel die Zustandsberechnung, um mehrere Auswertungen derselben Datenquelle zu vermeiden.

1. Anwendungsfall: Anfangszustand und Zustandsänderung

Behalten wir den vorherigen Ausgangszustand bei...

WTF Is Reactivity !?

In einem solchen System hätte die Anfangszustandssyntax die folgende Form:

let a = observable.of({ firstName: "John", lastName: "Doe" });
const b = a.pipe(map((a) => a.firstName));
const c = a.pipe(map((a) => a.lastName));
const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Hinweis:React-Enthusiasten werden diese Syntax wahrscheinlich erkennen ?

Durch die Deklaration einer reaktiven Variablen entsteht ein Tupel: unveränderliche Variable auf der einen Seite; Aktualisierungsfunktion dieser Variablen andererseits. Die übrigen Anweisungen (in unserem Fall B, C und D) werden als abgeleitete Zustände betrachtet, da sie auf ihre jeweiligen Abhängigkeiten „lauschen“.

d.subscribe((value) => console.log(value));
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Das entscheidende Merkmal eines „faulen“ Systems ist, dass es Änderungen nicht sofort weitergibt, sondern nur, wenn dies ausdrücklich angefordert wird.

let a = { firstName: "John", lastName: "Doe" };
const b = a.firstName;
const c = a.lastName;
const d = `${b} ${c}`;
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

In einem „PULL“-Modell löst die Verwendung eines effect() (von einer Komponente) zum Protokollieren des Werts einer reaktiven Variablen (angegeben als Abhängigkeit) die Berechnung der Zustandsänderung aus:

  • D prüft, ob seine Abhängigkeiten (B und C) aktualisiert wurden;
  • B prüft, ob seine Abhängigkeit (A) aktualisiert wurde;
  • A gibt seinen Wert an B weiter (wobei der Wert von B berechnet wird);
  • C prüft, ob seine Abhängigkeit (A) aktualisiert wurde;
  • A gibt seinen Wert an C weiter (wobei der Wert von C berechnet wird)
  • B und C geben ihren jeweiligen Wert an D weiter (wobei der Wert von D berechnet wird);

WTF Is Reactivity !?

Eine Optimierung dieses Systems ist bei der Abfrage von Abhängigkeiten möglich. Tatsächlich wird A im obigen Szenario zweimal abgefragt, um festzustellen, ob es aktualisiert wurde. Die erste Abfrage könnte jedoch ausreichen, um festzustellen, ob sich der Status geändert hat. C müsste diese Aktion nicht ausführen... Stattdessen könnte A nur seinen Wert senden.

2. Anwendungsfall: Nächste Iteration

Komplizieren wir den Zustand etwas, indem wir eine zweite reaktive Variable „root“ hinzufügen.

let a = observable.of({ firstName: "John", lastName: "Doe" });
const b = a.pipe(map((a) => a.firstName));
const c = a.pipe(map((a) => a.lastName));
const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Noch einmal verschiebt das System die Zustandsberechnung, bis sie explizit angefordert wird. Mit dem gleichen Effekt wie zuvor löst das Aktualisieren einer neuen reaktiven Variablen die folgenden Schritte aus:

  • D prüft, ob seine Abhängigkeiten (B und C) aktualisiert wurden ;
  • B prüft, ob seine Abhängigkeit (A) aktualisiert wurde ;
  • C prüft, ob seine Abhängigkeiten (A und E) aktualisiert wurden ;
  • E gibt seinen Wert an C weiter und C ruft den Wert von A durch Memoisierung ab (Berechnung des Werts von C) ;
  • C gibt seinen Wert an D weiter und D ruft den Wert von B durch Memoisierung ab (Berechnung des Werts von D) ;

WTF Is Reactivity !?

Da sich der Wert von A nicht geändert hat, ist eine Neuberechnung dieser Variablen nicht erforderlich (dasselbe gilt für den Wert von B). In solchen Fällen verbessert die Verwendung von Memoisierungsalgorithmen die Leistung während der Zustandsberechnung.

PUSH-PULL, auch bekannt als „feinkörnige“ Reaktivität

Das letzte Reaktivitätsmodell ist das „PUSH-PULL“-System. Der Begriff „PUSH“ spiegelt die sofortige Weitergabe von Änderungsbenachrichtigungen wider, während „PULL“ sich auf das Abrufen der Statuswerte bei Bedarf bezieht. Dieser Ansatz steht in engem Zusammenhang mit der sogenannten „feinkörnigen“ Reaktivität, die den folgenden Prinzipien folgt:

  • Deklaration reaktiver Variablen (wir sprechen von reaktiven Grundelementen)
  • Abhängigkeiten werden auf atomarer Ebene verfolgt
  • Die Verbreitung von Veränderungen erfolgt sehr zielgerichtet

Beachten Sie, dass diese Art der Reaktivität nicht nur beim „PUSH-PULL“-Modell auftritt. Unter feinkörniger Reaktivität versteht man die präzise Verfolgung von Systemabhängigkeiten. Es gibt also PUSH und PULL Reaktivitätsmodelle, die ebenfalls auf diese Weise funktionieren (ich denke an Jotai oder Recoil.

).

1. Anwendungsfall: Anfangszustand und Zustandsänderung

Immer noch basierend auf dem vorherigen Anfangszustand... Die Deklaration eines Anfangszustandes in einem „feinkörnigen“ Reaktivitätssystem würde wie folgt aussehen:

let a = { firstName: "John", lastName: "Doe" };
const b = a.firstName;
const c = a.lastName;
const d = `${b} ${c}`;
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Hinweis: Die Verwendung des Signalschlüsselworts ist hier nicht nur anekdotisch ?

In Bezug auf die Syntax ist es dem „PUSH“-Modell sehr ähnlich, es gibt jedoch einen bemerkenswerten und wichtigen Unterschied: Abhängigkeiten! In einem „feinkörnigen“ Reaktivitätssystem ist es nicht notwendig, die zur Berechnung eines abgeleiteten Zustands erforderlichen Abhängigkeiten explizit zu deklarieren, da diese Zustände implizit die von ihnen verwendeten Variablen verfolgen. In unserem Fall verfolgen B und C automatisch Änderungen am Wert von A und D verfolgt Änderungen sowohl an B als auch an C.

let a = observable.of({ firstName: "John", lastName: "Doe" });
const b = a.pipe(map((a) => a.firstName));
const c = a.pipe(map((a) => a.lastName));
const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

In einem solchen System ist die Aktualisierung einer reaktiven Variablen effizienter als in einem einfachen „PUSH“-Modell, da die Änderung automatisch an die davon abhängigen abgeleiteten Variablen weitergegeben wird (nur als Benachrichtigung, nicht). der Wert selbst).

d.subscribe((value) => console.log(value));
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Dann werden bei Bedarf (nehmen wir das Beispiel Logger) durch die Verwendung von D innerhalb des Systems die Werte der zugehörigen Wurzelzustände (in unserem Fall A) abgerufen und die Werte berechnet ​der abgeleiteten Zustände (B und C) und schließlich D auswerten. Ist das nicht eine intuitive Funktionsweise?

WTF Is Reactivity !?

2. Anwendungsfall: Nächste Iteration

Betrachten wir den folgenden Zustand:

a.next({ firstName: "Jane", lastName: "Doe" });
Nach dem Login kopieren
Nach dem Login kopieren

Auch hier ermöglicht der „feinkörnige“ Aspekt des PUSH-PULL-Systems die automatische Verfolgung jedes Zustands. Der abgeleitete Zustand C verfolgt nun die Wurzelzustände A und E. Durch das Aktualisieren der Variablen E werden die folgenden Aktionen ausgelöst:

  • Zustandsänderung des reaktiven Primitivs E!
  • Gezielte Änderungsbenachrichtigung (E nach D über C);
  • E gibt seinen Wert an C weiter und C ruft den Wert von A durch Memoisierung ab (Berechnung des Werts von C);
  • C gibt seinen Wert an D weiter und D ruft den Wert von B durch Memoisierung ab (Berechnung des Werts von D);

WTF Is Reactivity !?

Dies ist die vorherige Verknüpfung reaktiver Abhängigkeiten miteinander, die dieses Modell so effizient macht!

Tatsächlich wird das Framework in einem klassischen „PULL“-System (wie z. B. dem Virtual DOM von React) beim Aktualisieren eines reaktiven Status von einer Komponente über die Änderung benachrichtigt (wodurch ein „ diffing"-Phase). Dann berechnet das Framework bei Bedarf (und verzögert) die Änderungen, indem es den reaktiven Abhängigkeitsbaum durchläuft. jedes Mal, wenn eine Variable aktualisiert wird! Diese „Entdeckung“ des Zustands der Abhängigkeiten ist mit erheblichen Kosten verbunden...

Bei einem „feinkörnigen“ Reaktivitätssystem (wie Signalen) benachrichtigt die Aktualisierung reaktiver Variablen/Primitive automatisch jeden damit verknüpften abgeleiteten Zustand über die Änderung. Daher besteht keine Notwendigkeit, die damit verbundenen Abhängigkeiten (wieder) zu entdecken; Die Staatsverbreitung ist gezielt!

Fazit(.value)

Im Jahr 2024 haben sich die meisten Web-Frameworks entschieden, ihre Funktionsweise zu überdenken, insbesondere im Hinblick auf ihr Reaktivitätsmodell. Dieser Wandel hat sie im Allgemeinen effizienter und wettbewerbsfähiger gemacht. Andere entscheiden sich dafür, (noch) hybrid zu sein (ich denke hier an Vue), was sie in vielen Situationen flexibler macht.

Abschließend, unabhängig vom gewählten Modell, basiert meiner Meinung nach ein (gutes) reaktives System auf ein paar Hauptregeln:

  1. Das System verhindert inkonsistente abgeleitete Zustände;
  2. Die Verwendung eines Zustands innerhalb des Systems führt zu einem reaktiv abgeleiteten Zustand;
  3. Das System minimiert übermäßige Arbeit ;
  4. Und: „Für einen gegebenen anfänglichen Zustand wird das Endergebnis des Systems immer dasselbe sein, ganz gleich, welchem ​​Weg der Zustand folgt! „

Dieser letzte Punkt, der als grundlegendes Prinzip der deklarativen Programmierung interpretiert werden kann, ist, dass ein (gutes) reaktives System meiner Meinung nach deterministisch sein muss! Dies ist der „Determinismus“, der ein reaktives Modell unabhängig von der Komplexität des Algorithmus zuverlässig, vorhersehbar und einfach in technischen Projekten im großen Maßstab einsetzbar macht.

Das obige ist der detaillierte Inhalt vonWTF ist Reaktivität!?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage