Schnelleres Reagieren mit staatlichem Co-Hosting

In dem Artikel geht es um die Colokation von Bundesstaaten, dh um die Colokation von Bundesstaaten . Dieser Begriff könnte auch als Colokation von Bundesstaaten oder Colokation von Bundesstaaten übersetzt werden.


Einer der Hauptgründe für die Verlangsamung einer React-Anwendung ist ihr globaler Status. Ich werde dies anhand eines Beispiels einer sehr einfachen Anwendung zeigen, wonach ich ein Beispiel geben werde, das dem wirklichen Leben näher kommt.


Hier ist eine einfache Anwendung, in der Sie einen Namen für den Hund eingeben können (wenn das Fenster nicht funktioniert, ist hier der Link ):



Wenn Sie mit dieser Anwendung spielen, werden Sie bald feststellen, dass sie sehr langsam funktioniert. Bei der Interaktion mit einem Eingabefeld treten merkliche Leistungsprobleme auf. In einer solchen Situation können Sie einen Rettungsring in Form von React.memo und ihn mit allen Komponenten mit einem langsamen Rendering umwickeln. Aber lassen Sie uns versuchen, dieses Problem anders zu lösen.


Hier ist der Code für diese Anwendung:


 function sleep(time) { const done = Date.now() + time while (done > Date.now()) { // ... } } //      // -       function SlowComponent({time, onChange}) { sleep(time) return ( <div> Wow, that was{' '} <input value={time} type="number" onChange={e => onChange(Number(e.target.value))} /> ms slow </div> ) } function DogName({time, dog, onChange}) { return ( <div> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => onChange(e.target.value)} /> <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> </div> ) } function App() { //   " " (global state) const [dog, setDog] = React.useState('') const [time, setTime] = React.useState(200) return ( <div> <DogName time={time} dog={dog} onChange={setDog} /> <SlowComponent time={time} onChange={setTime} /> </div> ) } 

Wenn Sie den Artikel über Colocation ( Kollokation ) nicht gelesen haben, empfehle ich Ihnen, ihn zu lesen. Da wir wissen, dass Co-Hosting die Arbeit mit unserer Anwendung erleichtern kann, wenden wir dieses Prinzip bei der Arbeit mit Staaten an.


Beachten Sie den Code unserer Anwendung, nämlich den Zeitstatus - er wird von jeder Komponente unserer Anwendung verwendet, daher wurde er auf die App Komponente (die Komponente, die unsere gesamte Anwendung umschließt) angehoben ( lift - raise the state ). Der Status "dog" ( dog und setDog ) wird jedoch nur von einer Komponente verwendet. Er wird in der App Komponente nicht benötigt. Verschieben wir ihn in die DogName Komponente:


 function DogName({time}) { // <-    const [dog, setDog] = React.useState('') // <-   return ( <div> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => setDog(e.target.value)} /> <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> </div> ) } function App() { //   " " (global state) const [time, setTime] = React.useState(200) return ( <div> <DogName time={time} /> // <-    <SlowComponent time={time} onChange={setTime} /> </div> ) } 

Und hier ist unser Ergebnis (wenn das Fenster nicht funktioniert, verlinken Sie ):



Wow! Das Eingeben eines Namens geht jetzt VIEL schneller. Darüber hinaus ist das Bauteil durch die gemeinsame Platzierung wartungsfreundlicher geworden. Aber warum hat es schneller geklappt?


Sie sagen, dass der beste Weg, etwas schnell zu erledigen, darin besteht, so wenig wie möglich zu tun. Genau das passiert hier. Wenn wir den Status verwalten, der sich ganz oben im Komponentenbaum befindet, macht jede Aktualisierung dieses Status den gesamten Baum ungültig. Die Reaktion weiß nicht, was sich geändert hat. Aus diesem Grund muss er alle Komponenten überprüfen, um festzustellen, ob sie DOM-Aktualisierungen benötigen. Dieser Prozess ist nicht kostenlos und verbraucht Ressourcen (insbesondere, wenn Sie absichtlich langsame Komponenten haben). Wenn Sie jedoch den Status im Komponentenbaum so niedrig wie möglich verschieben, wie wir es mit dem Status dog und der Komponente DogName , führt React weniger Überprüfungen durch. Die Komponente SlowComponent die wir absichtlich verlangsamt haben, wird von React nicht überprüft, da React weiß, dass diese Komponente die Ausgabe weiterhin nicht beeinflussen kann.


Kurz gesagt, früher, als der Name des Hundes geändert wurde, wurde jede Komponente auf Änderungen überprüft (neu gerendert). Und nach den Änderungen, die wir am Code vorgenommen hatten, überprüfte React nur die DogName Komponente. Dies hat zu einer deutlichen Steigerung der Produktivität geführt!


Im wirklichen Leben


Ich sehe, dass Entwickler in das Redux Global Repository oder in den globalen Kontext stellen, was eigentlich nicht global sein sollte. DogName wie der DogName aus dem obigen Beispiel sind häufig Stellen, an denen ein Leistungsproblem auftritt. Ich sehe oft, dass sich dieses Problem bei der Interaktion mit der Maus manifestiert (z. B. beim Anzeigen eines Tooltips über einem Diagramm oder über einer Datentabelle).


Eine Lösung für dieses Problem besteht darin, die Benutzerinteraktion abzubrechen (das heißt, wir warten, bis der Benutzer aufhört zu tippen, und wenden dann die Statusaktualisierung an). Manchmal ist dies das Beste, was wir tun können, aber es kann zu einer schlechten Benutzererfahrung führen (der zukünftige gleichzeitige Modus sollte die Notwendigkeit dazu minimieren. Siehe diese Demo von Dan Abramov ).


Eine andere von Entwicklern häufig verwendete Lösung ist die Verwendung eines der React-Rescue-Renderings, z. B. React.memo . Dies funktioniert in unserem weit hergeholten Beispiel sehr gut, da die React das erneute Rendern der SlowComponent überspringen kann. In der Praxis kann die Anwendung jedoch unter dem „Tod durch tausend Schnitte“ leiden, da in einer realen Anwendung die Verlangsamung in der Regel nicht durch eine Verzögerung verursacht wird Komponente, und aufgrund der unzureichend schnellen Arbeit vieler Komponenten, müssen Sie React.memo überall verwenden. Wenn Sie dies getan haben, müssen Sie useMemo und useCallback . Andernfalls ist die Arbeit, die Sie in React.memo , vergeblich. Diese Aktionen können das Problem lösen, erhöhen jedoch die Komplexität Ihres Codes erheblich und sind in der Tat immer noch weniger effektiv als das Freigeben von Zuständen, da die Reaktion jede Komponente (von oben beginnend) durchlaufen muss, um zu bestimmen, ob sie erneut gerendert werden muss.


Wenn Sie mit einem etwas weniger weit hergeholten Beispiel herumspielen möchten , gehen Sie hier zu codesandbox .


Was ist Co-Location?


Das Prinzip der gemeinsamen Platzierung lautet:


Der Code sollte sich so nahe wie möglich an dem Ort befinden, auf den er sich bezieht.

Um diesem Prinzip zu entsprechen, sollte sich unser DogName in der DogName Komponente befinden:


 function DogName({time}) { const [dog, setDog] = React.useState('') return ( <div> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => setDog(e.target.value)} /> <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> </div> ) } 

Was aber, wenn wir diese Komponente in mehrere Komponenten aufteilen? Wohin soll der Staat gehen? Die Antwort ist dieselbe: "so nah wie möglich an dem Ort, auf den sie sich bezieht", und dies wird die nächste gemeinsame Elternkomponente sein . Lassen Sie uns als Beispiel die DogName Komponente DogName , DogName input und p in verschiedenen Komponenten angezeigt werden:


 function DogName({time}) { const [dog, setDog] = React.useState('') return ( <div> <DogInput dog={dog} onChange={setDog} /> <DogFavoriteNumberDisplay time={time} dog={dog} /> </div> ) } function DogInput({dog, onChange}) { return ( <> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => onChange(e.target.value)} /> </> ) } function DogFavoriteNumberDisplay({time, dog}) { return ( <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> ) } 

Der DogInput kann nicht in die DogInput Komponente DogInput werden, da die DogFavoriteNumberDisplay Komponente auch Zugriff auf den Status benötigt. DogFavoriteNumberDisplay wechseln wir vom unteren zum oberen Rand des Komponentenbaums, bis wir das gemeinsame übergeordnete DogFavoriteNumberDisplay dieser beiden Komponenten finden und die DogFavoriteNumberDisplay organisieren.


All dies gilt für Situationen, in denen Sie den Status von Dutzenden von Komponenten auf einem bestimmten Bildschirm Ihrer Anwendung steuern müssen. Sie können dies sogar in den Kontext verschieben, um das Bohren von Requisiten zu vermeiden. Halten Sie diesen Kontext jedoch so nah wie möglich an dem Ort, zu dem er gehört, und behalten Sie dann weiterhin die gute Leistung (und Verwendbarkeit des Codes) bei, die die Freigabe bietet. Denken Sie daran, dass Sie nicht alle Kontexte auf der obersten Ebene Ihrer React-Anwendung platzieren müssen. Bewahren Sie sie dort auf, wo es am sinnvollsten ist.


Dies ist die Hauptidee meines anderen Beitrags, Application State Management with React . Halten Sie Ihre Zustände so nah wie möglich an dem Ort, an dem sie verwendet werden. Dies verbessert sowohl die Leistung als auch die Benutzerfreundlichkeit des Codes. Bei diesem Ansatz kann die Leistung Ihrer Anwendung nur durch besonders komplexe Interaktionen mit Oberflächenelementen beeinträchtigt werden.


Also, was zu verwenden, Kontexte oder Redux?


Wenn Sie "Ein einfacher Trick zum Optimieren von React Re-Renderings" lesen, können Sie sicherstellen, dass nur die Komponenten aktualisiert werden, die den geänderten Status verwenden. Auf diese Weise können Sie dieses Problem einfach umgehen. Bei der Verwendung des Editors treten jedoch weiterhin Leistungsprobleme auf. Das Problem ist, dass React-Redux von Ihnen erwartet, dass Sie die Empfehlungen befolgen, um ein unnötiges Rendern der verbundenen Komponenten zu vermeiden . Es besteht immer die Möglichkeit eines Fehlers: Sie können Komponenten versehentlich so konfigurieren, dass sie zu oft gerendert werden, wenn sich andere globale Zustände ändern. Und je größer Ihre Anwendung ist, desto negativer ist der Effekt, insbesondere wenn Sie dem Editor zu viele Status hinzufügen.


Es gibt Methoden, mit denen Sie diese Probleme vermeiden können. Verwenden memoized Reselect mapState beispielsweise memoized Reselect mapState , oder lesen Sie die Dokumentation des Editors, um weitere Informationen zur Verbesserung der Anwendungsleistung zu erhalten .


Es ist erwähnenswert, dass die gemeinsame Platzierung zusammen mit dem Editor durchgeführt werden kann. Verwenden Sie die Editoren nur für globale Zustände und für alles andere verwenden Sie die Kollokation. In den häufig gestellten Fragen des Editors finden Sie einige nützliche Regeln , anhand derer Sie entscheiden können, ob der Status im Editor funktionieren oder in der Komponente verbleiben soll .


Übrigens, wenn Sie Ihren Status in Domänen aufteilen (abhängig von der Domäne in mehreren Kontexten), ist das Problem weniger ausgeprägt.


Die Tatsache bleibt jedoch bestehen: Die gemeinsame Suche nach Status reduziert Leistungsprobleme und vereinfacht die Codewartung.


Entscheide, wo der Staat platziert werden soll


Entscheidungsbaum:


Wohin-zu-setzen-zustand


Textversion, falls das Bild nicht angezeigt werden kann:


  • 1 Wir beginnen mit der Entwicklung der Anwendung. Gehe zu 2
  • 2 Status in der Komponente. Gehe zu 3
  • 3 Wird die Bedingung nur von dieser Komponente verwendet?
    • Huh? Wir gehen zu 4
    • Nein? Benötigt dieser Status nur eine untergeordnete Komponente?
    • Huh? Verschieben Sie es in diese untergeordnete Komponente (verwenden Sie die gemeinsame Position). Wir gehen zu 3.
    • Nein? Benötigt dieser Status übergeordnete oder benachbarte Komponenten ("brüderliche" Komponenten, dh untergeordnete Komponenten derselben übergeordneten Komponente)?
      • Huh? Verschieben Sie den obigen Status in die übergeordnete Komponente. Gehe zu 3
      • Nein? Wir gehen zu 4
  • 4 Belassen Sie es wie es ist. Gehe zu 5
  • 5 Gibt es ein Problem beim Bohren von Propellern?
    • Huh? Wir verschieben diesen Status in den Kontextanbieter und rendern diesen Anbieter in der Komponente, in der der Status verwaltet wird. Gehe zu 6
    • Nein? Gehe zu 6
  • 6 Wir senden die Bewerbung. Wenn neue Anforderungen angezeigt werden, fahren Sie mit Schritt 1 fort

Es ist wichtig, dass dieser Prozess Teil Ihres regulären Prozesses zur Überarbeitung / Wartung von Anwendungen ist. Wenn Ihr Zustand nicht an der Stelle ausgelöst wird, an der er ausgelöst werden soll, funktioniert dieser Zustand nicht mehr ordnungsgemäß und Sie werden es bemerken. Wenn Sie jedoch nicht die Methode für die gemeinsame Position von Zuständen befolgen und die Zustände in der Hierarchie der Komponenten nicht verringern, funktioniert Ihre Anwendung weiterhin. Aus diesem Grund werden Sie Performance- und Verwaltbarkeitsprobleme, die sich allmählich ansammeln, nicht sofort bemerken.


Fazit


Im Allgemeinen verstehen Entwickler recht gut, wann es notwendig ist, den Status zu erhöhen ("Lifting State"), wenn dies erforderlich ist, aber wir verstehen nicht so gut, wann der Status gesenkt werden muss. Ich schlage vor, Sie werfen einen Blick auf den Code Ihrer Bewerbung und überlegen, wo der Staat durch Anwendung des Prinzips der "Kollokation" weggelassen werden könnte. Fragen Sie sich: " isOpen ich den isOpen Status eines modalen Fensters im Editor?" (die Antwort ist höchstwahrscheinlich nein).


Verwenden Sie das Prinzip der Kollokation, und Ihr Code wird einfacher und schneller.


Viel glück

Source: https://habr.com/ru/post/de485032/


All Articles