Heim > Web-Frontend > js-Tutorial > Veränderliche Ableitungen der Reaktivität

Veränderliche Ableitungen der Reaktivität

Linda Hamilton
Freigeben: 2024-10-24 08:22:02
Original
905 Leute haben es durchsucht

All diese Erkundungen zu Planung und Asynchronität haben mir klar gemacht, wie viel wir immer noch nicht über Reaktivität verstehen. Ein Großteil der Forschung stammte aus der Modellierung von Schaltkreisen und anderen Echtzeitsystemen. Es gab auch eine beträchtliche Menge an Erforschung von Paradigmen der funktionalen Programmierung. Ich habe das Gefühl, dass dies unsere moderne Sichtweise auf Reaktivität weitgehend geprägt hat.

Als ich Svelte 3 und später den React-Compiler zum ersten Mal sah, stellten die Leute in Frage, dass diese Frameworks in ihrer Darstellung feinkörnig seien. Und um ehrlich zu sein, haben sie viele gemeinsame Eigenschaften. Wenn wir die Geschichte mit Signalen und den abgeleiteten Grundelementen, die wir bisher gesehen haben, beenden würden, könnten Sie über Äquivalenz argumentieren, mit der Ausnahme, dass diese Systeme ihre Reaktivität nicht außerhalb ihrer UI-Komponenten leben lassen.

Aber es gibt einen Grund, warum Solid nie einen Compiler brauchte, um dies zu erreichen. Und warum es bis heute noch optimaler ist. Es handelt sich nicht um ein Implementierungsdetail. Es ist architektonisch. Es hängt mit der reaktiven Unabhängigkeit von UI-Komponenten zusammen, aber es ist mehr als das.


Veränderlich vs. unveränderlich

In der Definition die Fähigkeit, sich zu ändern bzw. nicht. Aber das ist nicht das, was wir meinen. Wir hätten eine ziemlich langweilige Software, wenn sich nie etwas ändern würde. Beim Programmieren geht es darum, ob ein Wert verändert werden kann. Wenn ein Wert nicht verändert werden kann, besteht die einzige Möglichkeit, den Wert einer Variablen zu ändern, darin, sie neu zuzuweisen.

Aus dieser Perspektive sind Signale von Natur aus unveränderlich. Sie wissen nur, ob sich etwas geändert hat, indem sie abfangen, wenn der neue Wert zugewiesen wird. Wenn jemand seinen Wert unabhängig verändern würde, würde nichts Reaktives passieren.

const [signal, setSignal] = createSignal({ a: 1 });

createEffect(() => console.log(signal().a)); // logs "1"

// does not trigger the effect
signal().a = 2; 

setSignal({ a: 3 }); // the effect logs "3"
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Unser reaktives System ist ein verbundener Graph unveränderlicher Knoten. Wenn wir Daten ableiten, geben wir den nächsten Wert zurück. Es könnte sogar mit dem vorherigen Wert berechnet werden.

const [log, setLog] = createSignal("start");

const allLogs = createMemo(prev => prev + log()); // derived value
createEffect(() => console.log(allLogs())); // logs "start"

setLog("-end"); // effect logs "start-end"
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Aber etwas Interessantes passiert, wenn wir Signale in Signale und Effekte in Effekte einfügen.

function User(user) {
  // make "name" a Signal
  const [name, setName] = createSignal(user.name);
  return { ...user, name, setName };
}

const [user, setUser] = createSignal(
  new User({ id: 1, name: "John" })
);

createEffect(() => {
  const u = user();
  console.log("User", u.id);
  createEffect(() => {
     console.log("Name", u.name());
  })
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser(new User({ id: 2, name: "Jack" })); 

// effect logs "Name Janet"
user().setName("Janet");
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Jetzt können wir nicht nur den Benutzer ändern, sondern auch den Namen eines Benutzers. Noch wichtiger ist, dass wir unnötige Arbeit überspringen können, wenn sich nur der Name ändert. Wir führen den äußeren Effekt nicht erneut aus. Dieses Verhalten hängt nicht davon ab, wo der Status deklariert wird, sondern davon, wo er verwendet wird.

Das ist unglaublich mächtig, aber es ist schwer zu sagen, dass unser System unveränderlich ist. Ja, das einzelne Atom ist es, aber durch die Verschachtelung haben wir eine für Mutationen optimierte Struktur geschaffen. Wenn wir etwas ändern, wird nur der genaue Teil des Codes ausgeführt, der ausgeführt werden muss.

Wir könnten die gleichen Ergebnisse ohne Verschachtelung erzielen, aber wir würden dies erreichen, indem wir zusätzlichen Code für diff:
ausführen

const [signal, setSignal] = createSignal({ a: 1 });

createEffect(() => console.log(signal().a)); // logs "1"

// does not trigger the effect
signal().a = 2; 

setSignal({ a: 3 }); // the effect logs "3"
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Hier gibt es einen ziemlich klaren Kompromiss. Unsere verschachtelte Version musste die eingehenden Daten abbilden, um die verschachtelten Signale zu erzeugen, und sie dann im Wesentlichen ein zweites Mal abbilden, um aufzuschlüsseln, wie auf die Daten in unabhängigen Effekten zugegriffen wurde. Unsere Diff-Version könnte die einfachen Daten verwenden, muss aber bei jeder Änderung den gesamten Code erneut ausführen und alle Werte vergleichen, um festzustellen, was sich geändert hat.

Angesichts der Tatsache, dass das Diffing ziemlich leistungsfähig ist und die Zuordnung über besonders tief verschachtelte Daten umständlich ist, haben sich die Leute im Allgemeinen für Letzteres entschieden. Reagieren ist im Grunde Folgendes. Die Differenzierung wird jedoch nur teurer, je größer unsere Datenmenge und die damit verbundene Arbeit ist.

Wenn man sich erst einmal damit abgefunden hat, zu streiten, ist es unvermeidlich. Wir verlieren Informationen. Sie können es in den Beispielen oben sehen. Wenn wir im ersten Beispiel den Namen auf „Janet“ setzen, weisen wir das Programm an, den Namen user().setName(„Janet“) zu aktualisieren. Im zweiten Update legen wir einen völlig neuen Benutzer fest und das Programm muss herausfinden, was sich überall dort geändert hat, wo der Benutzer verwendet wird.

Obwohl das Verschachteln umständlicher ist, wird niemals unnötiger Code ausgeführt. Was mich dazu inspirierte, Solid zu entwickeln, war die Erkenntnis, dass das größte Problem bei der Abbildung verschachtelter Reaktivität mit Proxies gelöst werden kann. Und reaktive Stores wurden geboren:

const [log, setLog] = createSignal("start");

const allLogs = createMemo(prev => prev + log()); // derived value
createEffect(() => console.log(allLogs())); // logs "start"

setLog("-end"); // effect logs "start-end"
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Viel besser.

Der Grund dafür ist, dass wir immer noch wissen, dass der Name aktualisiert wird, wenn wir User(user => user.name = "Janet") festlegen. Der Setter für die Namenseigenschaft wird getroffen. Wir erreichen diese granulare Aktualisierung, ohne unsere Daten zuzuordnen oder zu unterscheiden.

Warum ist das wichtig? Stellen Sie sich vor, Sie hätten stattdessen eine Liste mit Benutzern. Betrachten Sie eine unveränderliche Änderung:

function User(user) {
  // make "name" a Signal
  const [name, setName] = createSignal(user.name);
  return { ...user, name, setName };
}

const [user, setUser] = createSignal(
  new User({ id: 1, name: "John" })
);

createEffect(() => {
  const u = user();
  console.log("User", u.id);
  createEffect(() => {
     console.log("Name", u.name());
  })
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser(new User({ id: 2, name: "Jack" })); 

// effect logs "Name Janet"
user().setName("Janet");
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Wir erhalten ein neues Array mit allen vorhandenen Benutzerobjekten außer dem neuen mit dem aktualisierten Namen. Zu diesem Zeitpunkt weiß das Framework lediglich, dass sich die Liste geändert hat. Es muss die gesamte Liste durchlaufen, um festzustellen, ob Zeilen verschoben, hinzugefügt oder entfernt werden müssen oder ob sich eine Zeile geändert hat. Wenn es sich geändert hat, wird die Kartenfunktion erneut ausgeführt und die Ausgabe generiert, die mit dem, was derzeit im DOM ist, ersetzt/abgeglichen wird.

Erwägen Sie eine veränderliche Änderung:

const [user, setUser] = createSignal({ id: 1, name: "John" });

let prev;
createEffect(() => {
  const u = user();
  // diff values
  if (u.id !== prev?.id) console.log("User", u.id);
  if (u.name !== prev?.name) console.log("Name", u.name);

  // set previous
  prev = u;
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser({ id: 2, name: "Jack" }); 

// effect logs "Name Janet"
setUser({ id: 2, name: "Janet" });
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Wir geben nichts zurück. Stattdessen aktualisiert das eine Signal, das den Namen dieses Benutzers darstellt, den spezifischen Effekt, der den Ort aktualisiert, an dem wir den Namen anzeigen, und führt ihn aus. Keine Listenerholung. Keine Listenunterschiede. Keine Reihenerholung. Keine DOM-Unterschiede.

Indem wir die veränderliche Reaktivität als erstklassigen Bürger behandeln, erhalten wir eine Autorenerfahrung, die dem unveränderlichen Zustand ähnelt, jedoch mit Fähigkeiten, die selbst der intelligenteste Compiler nicht erreichen kann. Aber wir sind heute nicht hier, um genau über reaktive Stores zu sprechen. Was hat das mit Ableitungen zu tun?


Ableitungen erneut betrachten

Abgeleitete Werte, wie wir sie kennen, sind unveränderlich. Wir haben eine Funktion, die bei jeder Ausführung den nächsten Status zurückgibt.

state = fn(state)

Wenn sich die Eingabe ändert, werden sie erneut ausgeführt und Sie erhalten den nächsten Wert. Sie spielen in unserem reaktiven Diagramm eine Reihe wichtiger Rollen.

Erstens dienen sie als Erinnerungspunkt. Wir können uns die Arbeit bei teuren oder asynchronen Berechnungen ersparen, wenn wir erkennen, dass sich die Eingaben nicht geändert haben. Wir können einen Wert mehrmals verwenden, ohne ihn neu zu berechnen.

Zweitens fungieren sie als Konvergenzknoten. Sie sind die „Joins“ in unserem Diagramm. Sie verbinden mehrere verschiedene Quellen miteinander und definieren ihre Beziehung. Dies ist der Schlüssel zur gemeinsamen Aktualisierung der Dinge, aber es liegt auch nahe, dass bei einer begrenzten Anzahl von Quellen und einer immer größeren Anzahl von Abhängigkeiten zwischen ihnen irgendwann alles miteinander verflochten wäre.

Es macht sehr viel Sinn. Bei abgeleiteten unveränderlichen Datenstrukturen gibt es nur „Joins“, keine „Forks“. Wenn die Komplexität zunimmt, sind Sie dazu bestimmt, zu verschmelzen. Interessanterweise haben reaktive „Stores“ diese Eigenschaft nicht. Einzelne Teile werden unabhängig voneinander aktualisiert. Wie wenden wir dieses Denken also auf die Ableitung an?

Der Form folgen

Mutable Derivations in Reactivity

Andre Staltz hat vor einigen Jahren einen erstaunlichen Artikel veröffentlicht, in dem er alle Arten reaktiver/iterierbarer Grundelemente in einem einzigen Kontinuum verknüpfte. Push/Pull alles vereint in einem einzigen Modell.

Ich habe mich schon lange von der systematischen Denkweise inspirieren lassen, die Andre in diesem Artikel angewendet hat. Und ich habe schon lange mit den Themen zu kämpfen, die ich in dieser Serie behandele. Manchmal reicht es aus, zu verstehen, dass der Designraum vorhanden ist, um die richtige Erkundung zu ermöglichen. Manchmal ist die Form der Lösung alles, was Sie zunächst verstehen müssen.


Mir ist zum Beispiel schon vor langer Zeit klar geworden, dass wir, wenn wir die Synchronisierung für bestimmte Zustandsaktualisierungen vermeiden wollten, eine Möglichkeit brauchten, einen beschreibbaren Zustand abzuleiten. Es blieb mehrere Jahre lang im Hinterkopf, aber schließlich schlug ich eine beschreibbare Ableitung vor.

const [signal, setSignal] = createSignal({ a: 1 });

createEffect(() => console.log(signal().a)); // logs "1"

// does not trigger the effect
signal().a = 2; 

setSignal({ a: 3 }); // the effect logs "3"
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren


Die Idee ist, dass es immer von seiner Quelle zurückgesetzt werden kann, aber man kann darüber hinaus kurzlebige Aktualisierungen anwenden, bis sich die Quelle das nächste Mal ändert. Warum sollte dies anstelle eines Effekts verwendet werden?

const [log, setLog] = createSignal("start");

const allLogs = createMemo(prev => prev + log()); // derived value
createEffect(() => console.log(allLogs())); // logs "start"

setLog("-end"); // effect logs "start-end"
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren


Denn wie im ersten Teil dieser Serie ausführlich erläutert, konnte das Signal hier nie wissen, dass es von props.field abhängt. Dadurch wird die Konsistenz des Diagramms zerstört, da wir seine Abhängigkeiten nicht zurückverfolgen können. Intuitiv wusste ich, dass diese Fähigkeit freigeschaltet wird, wenn der Lesevorgang in dasselbe Grundelement eingefügt wird. Tatsächlich ist createWritable heute vollständig im Userland umsetzbar.

<script> // Detect dark theme var iframe = document.getElementById('tweet-1252839841630314497-983'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1252839841630314497&theme=dark" } </script>
const [signal, setSignal] = createSignal({ a: 1 });

createEffect(() => console.log(signal().a)); // logs "1"

// does not trigger the effect
signal().a = 2; 

setSignal({ a: 3 }); // the effect logs "3"
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Es ist nur ein Signal höherer Ordnung. Ein Signal der Signale oder wie Andre es nannte, eine Kombination aus „Getter-Getter“ und „Getter-Setter“. Wenn der übergebene fn ausgeführt wird, verfolgt ihn die äußere Ableitung (createMemo) und erstellt ein Signal. Immer wenn sich diese Abhängigkeiten ändern, wird ein neues Signal erstellt. Bis es ersetzt wird, ist dieses Signal jedoch aktiv und alles, was auf die zurückgegebene Getter-Funktion lauscht, abonniert sowohl die Ableitung als auch das Signal und behält die Abhängigkeitskette bei.

Wir sind hier gelandet, weil wir der Form der Lösung gefolgt sind. Und im Laufe der Zeit bin ich dieser Form gefolgt und glaube nun, wie am Ende des letzten Artikels angedeutet, dass es sich bei diesem veränderlichen abgeleiteten Grundelement weniger um eine beschreibbare Ableitung, sondern um ein abgeleitetes Signal handelt.

const [log, setLog] = createSignal("start");

const allLogs = createMemo(prev => prev + log()); // derived value
createEffect(() => console.log(allLogs())); // logs "start"

setLog("-end"); // effect logs "start-end"
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Aber wir betrachten immer noch unveränderliche Grundelemente. Ja, dies ist eine beschreibbare Ableitung, aber die Wertänderung und Benachrichtigung erfolgen weiterhin im großen Stil.


Das Problem mit der Differenzierung

Mutable Derivations in Reactivity

Intuitiv können wir erkennen, dass es eine Lücke gibt. Ich konnte Beispiele für Dinge finden, die ich lösen wollte, konnte aber nie auf einem einzigen Grundelement landen, um diesen Raum zu bewältigen. Mir wurde klar, dass ein Teil des Problems in der Form liegt.

Einerseits könnten wir abgeleitete Werte in Stores einfügen:

function User(user) {
  // make "name" a Signal
  const [name, setName] = createSignal(user.name);
  return { ...user, name, setName };
}

const [user, setUser] = createSignal(
  new User({ id: 1, name: "John" })
);

createEffect(() => {
  const u = user();
  console.log("User", u.id);
  createEffect(() => {
     console.log("Name", u.name());
  })
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser(new User({ id: 2, name: "Jack" })); 

// effect logs "Name Janet"
user().setName("Janet");
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Aber wie kann dies dynamisch seine Form ändern, dh verschiedene Getter generieren, ohne in den Store zu schreiben?

Andererseits könnten wir dynamische Formen aus Stores ableiten, deren Ausgabe wäre jedoch kein Store.

const [user, setUser] = createSignal({ id: 1, name: "John" });

let prev;
createEffect(() => {
  const u = user();
  // diff values
  if (u.id !== prev?.id) console.log("User", u.id);
  if (u.name !== prev?.name) console.log("Name", u.name);

  // set previous
  prev = u;
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser({ id: 2, name: "Jack" }); 

// effect logs "Name Janet"
setUser({ id: 2, name: "Janet" });
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Wenn alle abgeleiteten Werte durch die Übergabe einer Wrapper-Funktion entstehen, die den nächsten Wert zurückgibt, wie können wir dann jemals Änderungen isolieren? Bestenfalls könnten wir die neuen Ergebnisse von den vorherigen unterscheiden und granulare Aktualisierungen nach außen anwenden. Aber das ging davon aus, dass wir schon immer unterschiedlich sein wollten.

Ich habe einen Artikel vom Signia-Team über inkrementelle Berechnungen gelesen, die so etwas wie die For-Komponente von Solid auf generische Weise implementieren. Abgesehen davon, dass die Logik nicht einfacher ist, ist mir jedoch Folgendes aufgefallen:

  • Es ist ein einzelnes unveränderliches Signal. Verschachtelte Änderungen können nicht unabhängig voneinander ausgelöst werden.

  • Jeder Knoten in der Kette muss teilnehmen. Jeder muss sein Quell-Diff anwenden, um aktualisierte Werte zu realisieren und, mit Ausnahme des Endknotens, sein Diff zur Weitergabe erzeugen.

Beim Umgang mit unveränderlichen Daten. Referenzen gehen verloren. Unterschiede helfen dabei, diese Informationen zurückzubekommen, aber dann tragen Sie die Kosten für die gesamte Kette. Und in manchen Fällen, etwa bei frischen Daten vom Server, gibt es keine stabilen Referenzen. Etwas muss die Modelle „verschlüsseln“, und das ist in Immer, das im Beispiel verwendet wird, nicht vorhanden. React hat diese Fähigkeit.

Da kam mir der Gedanke, dass diese Bibliothek für React erstellt wurde. Die Annahmen waren bereits verankert, dass es weitere Unterschiede geben würde. Sobald Sie sich damit abfinden, unterschiedlich zu sein, erzeugen Unterschiede immer mehr Unterschiede. Das ist die unvermeidliche Wahrheit. Sie hatten ein System geschaffen, um die schwere Arbeit zu vermeiden, indem sie zusätzliche Kosten auf das gesamte System verteilten.

Mutable Derivations in Reactivity

Ich hatte das Gefühl, dass ich zu schlau sein wollte. Der „schlechte“ Ansatz ist zwar nicht nachhaltig, aber zweifellos der leistungsfähigere.


Die große vereinheitlichende Theorie der (feinkörnigen) Reaktivität

Mutable Derivations in Reactivity

Es ist nichts Falsches daran, Dinge unveränderlich zu modellieren. Aber es gibt eine Lücke.

Also lasst uns der Form folgen:

const [signal, setSignal] = createSignal({ a: 1 });

createEffect(() => console.log(signal().a)); // logs "1"

// does not trigger the effect
signal().a = 2; 

setSignal({ a: 3 }); // the effect logs "3"
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Es wird deutlich, dass die abgeleitete Funktion dieselbe Form hat wie die Signalsetzerfunktion. In beiden Fällen übergeben Sie den vorherigen Wert und geben den neuen Wert zurück.

Warum machen wir das nicht mit Stores?

const [log, setLog] = createSignal("start");

const allLogs = createMemo(prev => prev + log()); // derived value
createEffect(() => console.log(allLogs())); // logs "start"

setLog("-end"); // effect logs "start-end"
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Wir können sogar die abgeleiteten Quellen einbringen:

function User(user) {
  // make "name" a Signal
  const [name, setName] = createSignal(user.name);
  return { ...user, name, setName };
}

const [user, setUser] = createSignal(
  new User({ id: 1, name: "John" })
);

createEffect(() => {
  const u = user();
  console.log("User", u.id);
  createEffect(() => {
     console.log("Name", u.name());
  })
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser(new User({ id: 2, name: "Jack" })); 

// effect logs "Name Janet"
user().setName("Janet");
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Hier besteht eine Symmetrie. Unveränderliche Änderungen erzeugen immer den nächsten Zustand, und veränderliche Änderungen mutieren den aktuellen Zustand in den nächsten Zustand. Auch nicht. Wenn sich die Priorität der unveränderlichen Ableitung (Memo) ändert, wird die gesamte Referenz ersetzt und alle Nebenwirkungen werden ausgeführt. Wenn sich die Priorität der veränderlichen Ableitung (Projektion) ändert, werden nur Dinge aktualisiert, die speziell auf die Priorität hören.


Projektionen erkunden

Unveränderlicher Wandel ist in seinen Abläufen konsistent, da er unabhängig von den Änderungen nur den nächsten Zustand aufbauen muss. Mutable kann je nach Änderung unterschiedliche Vorgänge haben. Unveränderliche Änderungen verfügen immer über den unveränderten vorherigen Zustand, mit dem sie arbeiten können, veränderliche Änderungen hingegen nicht. Dies wirkt sich auf die Erwartungen aus.

Wir können dies im vorherigen Abschnitt anhand der Notwendigkeit sehen, im Beispiel „reconcile“ zu verwenden. Wenn mit Projections ein völlig neues Objekt übergeben wird, geben Sie sich nicht damit zufrieden, alles zu ersetzen. Sie müssen die Änderungen schrittweise anwenden. Je nachdem, welche Updates erforderlich sind, kann es auf unterschiedliche Weise mutieren. Sie können alle Änderungen jedes Mal übernehmen und die internen Gleichheitsprüfungen eines Shops nutzen:

const [user, setUser] = createSignal({ id: 1, name: "John" });

let prev;
createEffect(() => {
  const u = user();
  // diff values
  if (u.id !== prev?.id) console.log("User", u.id);
  if (u.name !== prev?.name) console.log("Name", u.name);

  // set previous
  prev = u;
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser({ id: 2, name: "Jack" }); 

// effect logs "Name Janet"
setUser({ id: 2, name: "Janet" });
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Aber das wird schnell unerschwinglich, da es nur oberflächlich funktioniert. Eine Versöhnung (Differenzierung) ist immer eine Option. Aber oft wollen wir nur das anwenden, was wir brauchen. Dies führt zu komplizierterem Code, kann aber viel effizienter sein.

Wenn wir einen Auszug aus dem Trello-Klon von Solid ändern, können wir eine Projektion verwenden, um entweder jedes optimistische Update einzeln anzuwenden oder das Board mit dem neuesten Update vom Server abzugleichen.

const [signal, setSignal] = createSignal({ a: 1 });

createEffect(() => console.log(signal().a)); // logs "1"

// does not trigger the effect
signal().a = 2; 

setSignal({ a: 3 }); // the effect logs "3"
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Dies ist leistungsstark, da es nicht nur Referenzen in der Benutzeroberfläche beibehält, sodass nur granulare Aktualisierungen erfolgen, sondern auch Mutationen (optimistische Aktualisierungen) inkrementell anwendet, ohne zu klonen und zu unterscheiden. Komponenten müssen also nicht nur nicht erneut ausgeführt werden, sondern es ist auch nicht erforderlich, bei jeder Änderung den gesamten Zustand der Platine wiederholt zu rekonstruieren, um erneut festzustellen, dass sich nur sehr wenig geändert hat. Und schließlich, wenn es einen Unterschied machen muss, wenn der Server schließlich unsere neuen Daten zurückgibt, wird ein Unterschied zu dieser aktualisierten Projektion gemacht. Referenzen bleiben erhalten und es muss nichts neu gerendert werden.

Obwohl ich glaube, dass dieser Ansatz in Zukunft ein großer Gewinn für Echtzeit- und Local-First-Systeme sein wird, nutzen wir Projektionen bereits heute, vielleicht ohne es zu merken. Betrachten Sie reaktive Kartenfunktionen, die Signale für den Index enthalten:

const [log, setLog] = createSignal("start");

const allLogs = createMemo(prev => prev + log()); // derived value
createEffect(() => console.log(allLogs())); // logs "start"

setLog("-end"); // effect logs "start-end"
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Der Index wird auf Ihre Liste der Zeilen projiziert, die keinen Index als reaktive Eigenschaft enthalten. Jetzt ist dieses Grundelement so grundlegend, dass ich es wahrscheinlich nicht mit createProjection implementieren werde, aber es ist wichtig zu verstehen, dass es kategorisch eins ist.

Ein weiteres Beispiel ist die obskure createSelector-API von Solid. Damit können Sie den Auswahlstatus auf leistungsstarke Weise auf eine Liste von Zeilen projizieren, sodass durch eine Änderung der Auswahl nicht jede Zeile aktualisiert wird. Dank eines formalisierten Projektionsgrundelements benötigen wir kein spezielles Grundelement mehr:

function User(user) {
  // make "name" a Signal
  const [name, setName] = createSignal(user.name);
  return { ...user, name, setName };
}

const [user, setUser] = createSignal(
  new User({ id: 1, name: "John" })
);

createEffect(() => {
  const u = user();
  console.log("User", u.id);
  createEffect(() => {
     console.log("Name", u.name());
  })
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser(new User({ id: 2, name: "Jack" })); 

// effect logs "Name Janet"
user().setName("Janet");
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Dadurch wird eine Karte erstellt, in der Sie nach ID suchen, aber nur die ausgewählte Zeile vorhanden ist. Da es sich um einen Proxy für die Lebensdauer des Abonnements handelt, können wir nicht vorhandene Eigenschaften verfolgen und sie dennoch benachrichtigen, wenn sie aktualisiert werden. Durch Ändern der SelectionId werden höchstens zwei Zeilen aktualisiert: die bereits ausgewählte und die neue, die ausgewählt wird. Wir verwandeln eine O(n)-Operation in O(2).

Als ich mehr mit diesem Grundelement spielte, wurde mir klar, dass es nicht nur eine direkte veränderliche Ableitung ermöglichte, sondern auch zur dynamischen Durchleitung von Reaktivität verwendet werden konnte.

const [user, setUser] = createSignal({ id: 1, name: "John" });

let prev;
createEffect(() => {
  const u = user();
  // diff values
  if (u.id !== prev?.id) console.log("User", u.id);
  if (u.name !== prev?.name) console.log("Name", u.name);

  // set previous
  prev = u;
}); // logs "User 1", "Name John"

// effect logs "User 2", "Name Jack"
setUser({ id: 2, name: "Jack" }); 

// effect logs "Name Janet"
setUser({ id: 2, name: "Janet" });
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Diese Projektion legt nur die ID und den Namen des Benutzers offen, hält jedoch den Zugriff auf privateValue unzugänglich. Es macht etwas Interessantes, indem es einen Getter auf den Namen anwendet. Während also die Projektion erneut ausgeführt wird, wenn wir den gesamten Benutzer ersetzen, kann der Effekt nur durch die Aktualisierung des Benutzernamens ausgeführt werden, ohne dass die Projektion erneut ausgeführt wird.

Dies sind nur einige wenige Anwendungsfälle, und ich gebe zu, dass es etwas länger dauert, bis man sie verstanden hat. Aber ich habe das Gefühl, dass Projektionen das fehlende Glied in der Signals-Geschichte sind.


Abschluss

Mutable Derivations in Reactivity

Während dieser Erkundung der Ableitungen in der Reaktivität in den letzten Jahren habe ich viel gelernt. Meine gesamte Sicht auf Reaktivität hat sich geändert. Veränderlichkeit ist für mich zu einer klaren Säule der Reaktionsfähigkeit geworden, anstatt als notwendiges Übel angesehen zu werden. Etwas, das nicht durch kursbasierte Ansätze oder Compiler emuliert wird.

Dies ist eine kraftvolle Aussage, die einen grundlegenden Unterschied zwischen unveränderlicher und veränderlicher Reaktivität nahelegt. Ein Signal und ein Store sind nicht dasselbe. Es gibt auch keine Memos und Projektionen. Auch wenn wir vielleicht in der Lage sind, ihre APIs zu vereinheitlichen, sollten wir das vielleicht auch nicht tun.

Ich bin zu diesen Erkenntnissen gekommen, indem ich der API-Form von Solid gefolgt bin, aber andere Lösungen haben andere APIs für ihre Signale. Daher frage ich mich, ob sie zu den gleichen Schlussfolgerungen gekommen wären. Fairerweise muss man sagen, dass die Implementierung von Projektionen mit Herausforderungen verbunden ist, und die Geschichte ist hier noch nicht zu Ende. Aber ich denke, damit ist unsere Gedankenerkundung vorerst beendet. Ich habe viel Arbeit vor mir.

Danke, dass du mich auf dieser Reise begleitet hast.

Das obige ist der detaillierte Inhalt vonVeränderliche Ableitungen der 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
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage