Gleichzeitiger Modus in Reaktion: Anpassen von Webanwendungen an Geräte und Internetgeschwindigkeit

In diesem Artikel werde ich den Concurrent Mode in React vorstellen. Lassen Sie uns herausfinden, was es ist: Was sind die Funktionen, welche neuen Tools sind erschienen und wie kann der Betrieb von Webanwendungen mit ihrer Hilfe optimiert werden, damit alles für Benutzer fliegen kann. Der gleichzeitige Modus ist eine neue Funktion in React. Seine Aufgabe ist es, die Anwendung an verschiedene Geräte und Netzwerkgeschwindigkeiten anzupassen. Bisher ist der Concurrent Mode ein Experiment, das von den Entwicklern der Bibliothek geändert werden kann, was bedeutet, dass sich keine neuen Tools im Stall befinden. Ich habe dich gewarnt und jetzt lass uns gehen.



Derzeit gibt es zwei Einschränkungen beim Rendern von Komponenten: Prozessorleistung und Netzwerkdatenübertragungsrate. Immer wenn dem Benutzer etwas angezeigt werden muss, versucht die aktuelle Version von React, jede Komponente von Anfang bis Ende zu rendern. Es spielt keine Rolle, ob die Schnittstelle für einige Sekunden einfriert. Ähnlich verhält es sich mit der Datenübertragung. React wartet auf absolut alle Daten, die die Komponente benötigt, anstatt sie Stück für Stück zu zeichnen.







Das Wettbewerbsregime löst diese Probleme. Mit React können zuvor blockierte Vorgänge angehalten, priorisiert und sogar rückgängig gemacht werden, sodass Sie im gleichzeitigen Modus mit dem Rendern von Komponenten beginnen können, unabhängig davon, ob alle Daten empfangen wurden oder nur ein Teil davon.



Der gleichzeitige Modus ist die Faserarchitektur



Der Wettbewerbsmodus ist keine neue Sache, die die Entwickler plötzlich hinzugefügt haben, und alles hat genau dort funktioniert. Vorbereitet für die Veröffentlichung im Voraus. In Version 16 wurde die React-Engine auf eine Fibre-Architektur umgestellt, die im Prinzip dem Taskplaner im Betriebssystem ähnelt. Der Scheduler verteilt Rechenressourcen auf Prozesse. Er kann jederzeit wechseln, sodass der Benutzer die Illusion hat, dass die Prozesse parallel ablaufen.



Die Faserarchitektur macht dasselbe, jedoch mit Komponenten. Trotz der Tatsache, dass es sich bereits in React befindet, scheint sich die Fibre-Architektur in einer angehaltenen Animation zu befinden und nutzt ihre Funktionen nicht maximal aus. Im Wettbewerbsmodus wird es mit voller Leistung eingeschaltet.



Wenn Sie eine Komponente im normalen Modus aktualisieren, müssen Sie einen ganz neuen Rahmen auf dem Bildschirm zeichnen. Bis das Update abgeschlossen ist, wird dem Benutzer nichts angezeigt. In diesem Fall arbeitet React synchron. Faser verwendet ein anderes Konzept. Alle 16 ms gibt es einen Interrupt und eine Überprüfung: Hat sich der virtuelle Baum geändert, sind neue Daten erschienen? In diesem Fall sieht der Benutzer sie sofort.



Warum 16ms? Reaktionsentwickler bemühen sich, den Bildschirm mit einer Geschwindigkeit von fast 60 Bildern pro Sekunde neu zu zeichnen. Um 60 Updates in 1000 ms zu integrieren, müssen Sie sie ungefähr alle 16 ms durchführen. Daher die Figur. Der Wettbewerbsmodus ist sofort einsatzbereit und fügt neue Tools hinzu, die das Front-End-Leben verbessern. Ich werde Ihnen über jedes im Detail erzählen.



Spannung



Die Spannung wurde in Reaktion 16.6 als Mechanismus zum dynamischen Laden von Komponenten eingeführt. Im gleichzeitigen Modus bleibt diese Logik erhalten, es werden jedoch zusätzliche Möglichkeiten angezeigt. Spannung wird zu einem Mechanismus, der in Verbindung mit der Datenladebibliothek funktioniert. Wir fordern eine spezielle Ressource über die Bibliothek an und lesen Daten daraus.



Suspense liest gleichzeitig Daten, die noch nicht fertig sind. Wie? Wir fordern die Daten an und bis sie vollständig sind, beginnen wir bereits, sie in kleinen Stücken zu lesen. Das Coolste für Entwickler ist die Verwaltung der Reihenfolge, in der die geladenen Daten angezeigt werden. Mit Suspense können Sie Seitenkomponenten gleichzeitig und unabhängig voneinander anzeigen. Dies macht den Code unkompliziert: Sie müssen sich nur die Suspense-Struktur ansehen, um zu sehen, in welcher Reihenfolge die Daten angefordert werden.



Eine typische Lösung zum Laden von Seiten in "alten" React ist Fetch-On-Render. In diesem Fall fordern wir Daten nach dem Rendern in useEffect oder componentDidMount an. Dies ist Standardlogik, wenn keine Redux- oder andere Datenschicht vorhanden ist. Zum Beispiel möchten wir 2 Komponenten zeichnen, von denen jede Daten benötigt:



  • Komponentenanforderung 1
  • Erwartung…
  • Daten abrufen -> Komponente 1 rendern
  • Komponentenanforderung 2
  • Erwartung…
  • Daten abrufen -> Komponente 2 rendern


Bei diesem Ansatz wird die nächste Komponente erst angefordert, nachdem die erste gerendert wurde. Es ist lang und unpraktisch.



Betrachten wir einen anderen Weg, Fetch-Then-Render: Zuerst fordern wir alle Daten an, dann zeichnen wir die Seite.



  • Komponentenanforderung 1
  • Komponentenanforderung 2
  • Erwartung…
  • Komponente 1 abrufen
  • Komponente 2 abrufen
  • Komponenten-Rendering


In diesem Fall verschieben wir den Status der Anforderung irgendwo nach oben - wir delegieren ihn an die Bibliothek, um mit Daten zu arbeiten. Die Methode funktioniert gut, aber es gibt eine Nuance. Wenn das Laden einer der Komponenten viel länger dauert als die andere, sieht der Benutzer nichts, obwohl wir ihm bereits etwas zeigen könnten. Schauen wir uns einen Beispielcode aus der Demo mit zwei Komponenten an: Benutzer und Beiträge. Wir verpacken Komponenten in Suspense:



const resource = fetchData() // -    React
function Page({ resource }) {
    return (
        <Suspense fallback={<h1>Loading user...</h1>}>
            <User resource={resource} />
            <Suspense fallback={<h1>Loading posts...</h1>}>
                <Posts resource={resource} />
            </Suspense>
        </Suspense>
    )
}


Es scheint, dass dieser Ansatz dem Fetch-On-Render nahe kommt, als wir nach dem Rendern der ersten Komponente Daten angefordert haben. Durch die Verwendung von Suspense werden die Daten jedoch viel schneller abgerufen. Dies liegt daran, dass beide Anfragen parallel gesendet werden.



In Suspense können Sie den Fallback angeben, die Komponente, die angezeigt werden soll, und die von der Datenabrufbibliothek in der Komponente implementierte Ressource übergeben. Wir benutzen es so wie es ist. Innerhalb der Komponenten fordern wir Daten von der Ressource an und rufen die Lesemethode auf. Dies ist das Versprechen, das die Bibliothek für uns macht. Suspense wird verstehen, ob die Daten geladen wurden, und wenn ja, wird es angezeigt.



Beachten Sie, dass die Komponenten versuchen, Daten zu lesen, die noch empfangen werden:



function User() {
    const user = resource.user.read()
    return <h1>{user.name}</h1>
}
function Posts() {
    const posts = resource.posts.read()
    return //  
}


In den aktuellen Demos von Dan Abramov wird so etwas als Stub für eine Ressource verwendet .



read() {
    if (status === 'pending') {
        throw suspender
    } else if (status === 'error') {
        throw result
    } else if (status === 'success') {
        return result
    }
}




Wenn die Ressource noch geladen wird, wird das Promise-Objekt ausnahmsweise ausgelöst. Suspense fängt diese Ausnahme ab, erkennt, dass es sich um ein Versprechen handelt, und lädt weiter. Wenn anstelle eines Versprechens eine Ausnahme mit einem anderen Objekt eintritt, wird klar, dass die Anforderung fehlerhaft beendet wurde. Wenn das fertige Ergebnis zurückgegeben wird, zeigt Suspense es an. Für uns ist es wichtig, eine Ressource zu erhalten und eine Methode dafür aufzurufen. Wie es intern implementiert wird, ist eine Entscheidung der Bibliotheksentwickler. Hauptsache, Suspense versteht deren Implementierung.



Wann Daten anfordern? Fragen an der Spitze des Baumes ist keine gute Idee, da sie möglicherweise nie benötigt werden. Eine bessere Option ist, dies sofort zu tun, wenn Sie in Ereignishandlern navigieren. Erhalten Sie beispielsweise den Anfangszustand über einen Hook und fordern Sie Ressourcen an, sobald der Benutzer auf die Schaltfläche klickt.



So wird es im Code aussehen:



function App() {
    const [resource, setResource] = useState(initialResource)
    return (
        <>
            <Button text='' onClick={() => {
                setResource(fetchData())
            }}>
            <Page resource={resource} />
        </>
    );
}


Spannung ist unglaublich flexibel. Es kann verwendet werden, um Komponenten nacheinander anzuzeigen.



return (
    <Suspense fallback={<h1>Loading user...</h1>}>
        <User />
        <Suspense fallback={<h1>Loading posts...</h1>}>
            <Posts />
        </Suspense>
    </Suspense>
)


Oder gleichzeitig müssen beide Komponenten in eine Spannung eingewickelt werden.



return (
    <Suspense fallback={<h1>Loading user and posts...</h1>}>
        <User />
        <Posts />
    </Suspense>
)


Oder laden Sie die Komponenten getrennt voneinander, indem Sie sie in unabhängige Spannung einwickeln. Die Ressource wird über die Bibliothek geladen. Es ist sehr cool und praktisch.



return (
    <>
        <Suspense fallback={<h1>Loading user...</h1>}>
            <User />
        </Suspense>
        <Suspense fallback={<h1>Loading posts...</h1>}>
            <Posts />
        </Suspense>
    </>
)


Darüber hinaus erkennen die Fehlerbegrenzungskomponenten Fehler in Suspense. Wenn etwas schief gelaufen ist, können wir zeigen, dass der Benutzer geladen hat, die Beiträge jedoch nicht, und einen Fehler geben.



return (
    <Suspense fallback={<h1>Loading user...</h1>}>
        <User resource={resource} />
        <ErrorBoundary fallback={<h2>Could not fetch posts</h2>}>
            <Suspense fallback={<h1>Loading posts...</h1>}>
                <Posts resource={resource} />
            </Suspense>
        </ErrorBoundary>
    </Suspense>
)


Schauen wir uns nun andere Tools an, mit denen Sie die Vorteile des Wettbewerbsregimes voll ausschöpfen können.



SuspenseList



SuspenseList hilft gleichzeitig bei der Steuerung der Ladereihenfolge von Suspense. Wenn wir mehrere Suspense streng nacheinander ohne sie laden müssten, müssten sie ineinander verschachtelt sein:



return (
    <Suspense fallback={<h1>Loading user...</h1>}>
        <User />
        <Suspense fallback={<h1>Loading posts...</h1>}>
            <Posts />
            <Suspense fallback={<h1>Loading facts...</h1>}>
                <Facts />
            </Suspense>
        </Suspense>
    </Suspense>
)


SuspenseList macht dies viel einfacher:



return (
    <SuspenseList revealOrder="forwards" tail="collapsed">
        <Suspense fallback={<h1>Loading posts...</h1>}>
            <Posts />
        </Suspense>
        <Suspense fallback={<h1>Loading facts...</h1>}>
            <Facts />
        </Suspense>
    </Suspense>
)


Die Flexibilität von SuspenseList ist erstaunlich. Sie können SuspenseList nach Belieben ineinander verschachteln und die Ladereihenfolge anpassen, da dies für die Anzeige von Widgets und anderen Komponenten praktisch ist.



useTransition



Ein spezieller Hook, der die Aktualisierung der Komponente verschiebt, bis sie vollständig bereit ist, und den Zwischenladezustand entfernt. Wofür ist das? React ist bestrebt, den Übergang beim Zustandswechsel so schnell wie möglich zu gestalten. Aber manchmal ist es wichtig, sich Zeit zu nehmen. Wenn ein Teil der Daten in eine Benutzeraktion geladen wird, wird normalerweise zum Zeitpunkt des Ladens ein Loader oder ein Skelett angezeigt. Wenn die Daten sehr schnell eintreffen, hat der Lader nicht einmal eine halbe Umdrehung Zeit. Es wird blinken und dann verschwinden, und wir werden die aktualisierte Komponente zeichnen. In solchen Fällen ist es klüger, den Lader überhaupt nicht zu zeigen.



Hier kommt useTransition ins Spiel. Wie funktioniert es im Code? Wir rufen den useTransition-Hook auf und geben das Zeitlimit in Millisekunden an. Wenn die Daten nicht innerhalb der angegebenen Zeit eingehen, wird der Loader weiterhin angezeigt. Aber wenn wir sie schneller bekommen, wird es einen sofortigen Übergang geben.



function App() {
    const [resource, setResource] = useState(initialResource)
    const [startTransition, isPending] = useTransition({ timeoutMs: 2000 })
    return <>
        <Button text='' disabled={isPending} onClick={() => {
            startTransition(() => {
                setResource(fetchData())
            })
        }}>
        <Page resource={resource} />
    </>
}


Wenn wir zur Seite gehen, möchten wir manchmal den Loader nicht anzeigen, aber wir müssen noch etwas in der Benutzeroberfläche ändern. Blockieren Sie beispielsweise die Schaltfläche für die Dauer des Übergangs. Dann ist die Eigenschaft isPending nützlich - sie informiert Sie darüber, dass wir uns in der Übergangsphase befinden. Für den Benutzer erfolgt die Aktualisierung sofort, es ist jedoch wichtig zu beachten, dass die useTransition-Magie nur Komponenten betrifft, die in Suspense eingeschlossen sind. UseTransition selbst funktioniert nicht.



Übergänge sind in Schnittstellen üblich. Die für den Übergang verantwortliche Logik wäre großartig, um sie in die Schaltfläche zu nähen und in die Bibliothek zu integrieren. Wenn es eine Komponente gibt, die für Übergänge zwischen Seiten verantwortlich ist, können Sie den onClick, der über die Requisiten an die Schaltfläche in handleClick übergeben wird, umbrechen und den Status isDisabled anzeigen.



function Button({ text, onClick }) {
    const [startTransition, isPending] = useTransition({ timeoutMs: 2000 })

    function handleClick() {
        startTransition(() => {
            onClick()
        })
    }

    return <button onClick={handleClick} disabled={isPending}>text</button>
}


useDeferredValue



Es gibt also eine Komponente, mit der wir Übergänge machen. Manchmal tritt die folgende Situation auf: Der Benutzer möchte zu einer anderen Seite wechseln, wir haben einige Daten erhalten und sind bereit, sie anzuzeigen. Gleichzeitig unterscheiden sich die Seiten geringfügig voneinander. In diesem Fall wäre es logisch, die veralteten Benutzerdaten anzuzeigen, bis alles andere geladen ist.



Jetzt weiß React nicht wie: In der aktuellen Version können nur Daten aus dem aktuellen Status auf dem Bildschirm des Benutzers angezeigt werden. UseDeferredValue im gleichzeitigen Modus kann jedoch eine verzögerte Version eines Werts zurückgeben, veraltete Daten anstelle eines blinkenden Loaders anzeigen oder beim Booten zurückfallen. Dieser Hook nimmt den Wert an, für den wir die verzögerte Version und die Verzögerung in Millisekunden wünschen.



Die Schnittstelle wird super flüssig. Aktualisierungen können mit einer minimalen Datenmenge vorgenommen werden, und alles andere wird schrittweise geladen. Der Benutzer hat den Eindruck, dass die Anwendung schnell und reibungslos ist. In Aktion sieht useDeferredValue folgendermaßen aus:



function Page({ resource }) {
    const deferredResource = useDeferredValue(resource, { timeoutMs: 1000 })
    const isDeferred = resource !== deferredResource;
    return (
        <Suspense fallback={<h1>Loading user...</h1>}>
            <User resource={resource} />
            <Suspense fallback={<h1>Loading posts...</h1>}>
                <Posts resource={deferredResource} isDeferred={isDeferred}/>
            </Suspense>
        </Suspense>
    )
}


Sie können den Wert der Requisiten mit dem Wert vergleichen, der durch useDeferredValue erhalten wurde. Wenn sie sich unterscheiden, wird die Seite noch geladen.



Interessanterweise können Sie mit useDeferredValue den Trick des verzögerten Ladens nicht nur für Daten wiederholen, die über das Netzwerk übertragen werden, sondern auch, um das Einfrieren der Schnittstelle aufgrund umfangreicher Berechnungen zu beseitigen.



Warum ist das toll? Verschiedene Geräte arbeiten unterschiedlich. Wenn Sie eine Anwendung mit useDeferredValue auf einem neuen iPhone ausführen, erfolgt der Übergang von Seite zu Seite sofort, auch wenn die Seiten schwer sind. Bei Verwendung von entprellt wird die Verzögerung jedoch auch auf einem leistungsstarken Gerät angezeigt. UseDeferredValue und Concurrent Mode passen sich an die Hardware an: Wenn es langsam funktioniert, fliegt die Eingabe immer noch und die Seite selbst wird aktualisiert, wenn das Gerät dies zulässt.



Wie schalte ich ein Projekt in den gleichzeitigen Modus um?



Der Wettbewerbsmodus ist ein Modus, daher müssen Sie ihn aktivieren. Wie ein Kippschalter, mit dem Glasfaser voll ausgelastet ist. Wo fängst du an?



Wir entfernen das Erbe. Wir entfernen alle veralteten Methoden im Code und stellen sicher, dass sie nicht in den Bibliotheken enthalten sind. Wenn die Anwendung in React.StrictMode einwandfrei funktioniert, ist alles in Ordnung - der Umzug ist einfach. Die mögliche Komplikation sind Probleme innerhalb der Bibliotheken. In diesem Fall müssen Sie entweder auf eine neue Version aktualisieren oder die Bibliothek ändern. Oder das Wettbewerbsregime aufgeben. Nachdem Sie das Erbe beseitigt haben, müssen Sie nur noch die Wurzel wechseln.



Mit der Einführung des gleichzeitigen Modus stehen drei Root-Verbindungsmodi zur Verfügung:



  • Der alte

    ReactDOM.render(<App />, rootNode)

    Rendermodus ist veraltet, nachdem der Wettbewerbsmodus freigegeben wurde.
  • Blockierungsmodus

    ReactDOM.createBlockingRoot(rootNode).render(<App />)

    Als Zwischenstufe wird der Blockierungsmodus hinzugefügt, der den Zugang zu einigen Möglichkeiten für den Wettbewerbsmodus bei Projekten ermöglicht, bei denen Vermächtnisse oder andere Schwierigkeiten bei der Verlagerung vorliegen.
  • Wettbewerbsmodus

    ReactDOM.createRoot(rootNode).render(<App />)

    Wenn alles in Ordnung ist, gibt es kein Vermächtnis und das Projekt kann sofort umgeschaltet werden. Ersetzen Sie das Rendering im Projekt durch createRoot - und starten Sie in eine glänzende Zukunft.


Schlussfolgerungen



Blockierungsvorgänge in React werden durch Umschalten auf Glasfaser asynchronisiert. Es entstehen neue Tools, mit denen sich die Anwendung leicht an die Funktionen des Geräts und die Netzwerkgeschwindigkeit anpassen lässt:



  • Spannung, dank der Sie die Reihenfolge der Ladedaten festlegen können.
  • SuspenseList, was es noch bequemer macht.
  • Verwenden Sie Transition, um reibungslose Übergänge zwischen Suspense-Komponenten zu erstellen.
  • useDeferredValue - um veraltete Daten während E / A- und Komponentenaktualisierungen anzuzeigen


Versuchen Sie, mit dem gleichzeitigen Modus zu experimentieren, solange dieser noch nicht verfügbar ist. Im gleichzeitigen Modus erzielen Sie beeindruckende Ergebnisse: schnelles und reibungsloses Laden von Komponenten in beliebiger Reihenfolge, superflüssige Schnittstelle. Details sind in der Dokumentation beschrieben, es gibt auch Demos mit Beispielen, die Sie selbst studieren sollten. Und wenn Sie neugierig sind, wie die Glasfaserarchitektur funktioniert, finden Sie hier einen Link zu einem interessanten Vortrag.



Bewerten Sie Ihre Projekte - was kann mit den neuen Tools verbessert werden? Und wenn der Wettbewerbsmodus aus ist, können Sie sich frei bewegen. Alles wird großartig!



All Articles