So arbeiten Sie mit Reagieren Sie richtig, um einige häufige Fallstricke zu vermeiden

Macbook Pro-Tastatur

Eine Sache, die ich ziemlich oft höre, ist " Let's go for Redux " in unserer neuen React-App. Es hilft Ihnen beim Skalieren, und die App-Daten sollten sich nicht im lokalen Status "Reagieren" befinden, da sie ineffizient sind. Oder wenn Sie eine API aufrufen und das Versprechen noch aussteht, wird die Komponente abgemeldet und Sie erhalten den folgenden schönen Fehler.

Warnung: setState (oder forceUpdate) kann für eine nicht gemountete Komponente nicht aufgerufen werden. Dies ist ein No-Op, weist jedoch auf einen Speicherverlust in Ihrer Anwendung hin. Brechen Sie zum Beheben alle Abonnements und asynchronen Aufgaben in der componentWillUnmount-Methode ab.

Die Lösung, zu der die Leute normalerweise kommen, ist die Verwendung von Redux .Ich liebe Redux und die Arbeit, die Dan Abramov leistet, ist einfach unglaublich! Dieser Typ rockt großartig - ich wünschte, ich wäre genauso halb talentiert wie er.

Aber ich bin mir sicher, dass Dan, als er Redux machte, uns nur als Helfer ein Werkzeug in unseren Werkzeuggürtel gab. Es ist nicht der Alleskönner. Sie verwenden keinen Hammer, wenn Sie den Bolzen mit einem Schraubenzieher festschrauben können.

Dan stimmt sogar zu .

Ich liebe React und arbeite seit fast zwei Jahren daran. Bisher kein Bedauern. Beste Entscheidung aller Zeiten. Ich mag Vue und all die coolen Bibliotheken / Frameworks da draußen. Aber React hat einen besonderen Platz in meinem Herzen. Es hilft mir, mich auf die Arbeit zu konzentrieren, die ich machen soll, anstatt meine ganze Zeit mit DOM-Manipulationen zu verbringen. Und das auf die bestmögliche und effizienteste Weise. mit seiner effektiven Versöhnung.

Ich habe in den letzten Jahren viel gelernt und bei neuen und erfahrenen React-Entwicklern ein häufiges Problem festgestellt: React nicht richtig zu verwenden, wenn Abonnement- oder asynchrone Aufgaben ausgeführt werden. Ich habe das Gefühl, dass die Dokumentation in diesem Fall nicht gut ist, und deshalb habe ich beschlossen, diesen Artikel zu schreiben.

Ich werde zuerst über Abonnements sprechen und dann mit dem asynchronen Abbrechen von Aufgaben fortfahren, um Speicherverluste in React zu vermeiden (der Hauptzweck dieses Artikels). Wenn dies nicht behandelt wird, verlangsamt dies unsere App.

Kommen wir nun zu der schönen Fehlermeldung zurück, über die wir ursprünglich gesprochen haben:

Warnung: setState (oder forceUpdate) kann für eine nicht gemountete Komponente nicht aufgerufen werden. Dies ist ein No-Op, weist jedoch auf einen Speicherverlust in Ihrer Anwendung hin. Brechen Sie zum Beheben alle Abonnements und asynchronen Aufgaben in der componentWillUnmount-Methode ab.

Mein Ziel für diesen Artikel ist es sicherzustellen, dass niemand jemals mit diesem Fehler konfrontiert wird und nicht weiß, was er erneut tun soll.

Was wir behandeln werden

  • Löschen Sie Abonnements wie setTimeout / setInterval
  • Löschen Sie asynchrone Aktionen, wenn Sie eine XHR-Anforderung mit fetchoder wie Bibliotheken aufrufenaxios
  • Alternative Methoden, einige meinten, andere veraltet.

Bevor ich anfange, ein großes Dankeschön an Kent C Dodds , die derzeit coolste Person im Internet. Vielen Dank, dass Sie sich die Zeit genommen und der Community etwas zurückgegeben haben. Seine Youtube- PodcastsundDer Egghead-Kurs zu Advanced React Component Patterns ist erstaunlich. Schauen Sie sich diese Ressourcen an, wenn Sie den nächsten Schritt in Ihren Reaktionsfähigkeiten machen möchten.

Ich habe Kent nach einem besseren Ansatz gefragt, um zu vermeiden, dass setState beim Entfernen der Komponente aufgehoben wird, damit ich die Leistung von React besser optimieren kann. Er ging weit darüber hinaus und machte ein Video darüber. Wenn Sie eine Video-Person sind, sehen Sie sich das unten an. Es wird Ihnen Schritt für Schritt eine ausführliche Erklärung geben.

Jetzt können wir loslegen.

1: Abonnements löschen

Beginnen wir mit dem Beispiel:

Reden wir darüber, was gerade hier passiert ist. Ich möchte, dass Sie sich auf die counter.jsDatei konzentrieren, die den Zähler nach 3 Sekunden erhöht.

Dies führt in 5 Sekunden zu einem Fehler, da ich ein Abonnement abgemeldet habe, ohne es zu löschen. Wenn Sie den Fehler erneut anzeigen möchten, klicken Sie einfach auf die Schaltfläche Aktualisieren im CodeSandbox-Editor, um den Fehler in der Konsole anzuzeigen.

Ich habe meine Container-Datei, index.jsdie nach den ersten fünf Sekunden einfach die Zählerkomponente umschaltet.

Damit

- - - → Index.js - - - - → Counter.js

In meiner Index.js rufe ich Counter.js auf und mache dies einfach in meinem Rendering:

{showCounter ?  : null}

Das showCounterist ein Zustand , boolean , die Menge selbst nach den ersten 5 Sekunden , sobald die Komponente Halterungen (componentDidMount) auf falsch ist.

Die reale Sache, die unser Problem hier veranschaulicht, ist die counter.jsDatei, die die Anzahl alle 3 Sekunden erhöht. Nach den ersten 3 Sekunden wird der Zähler aktualisiert. Aber sobald es zum zweiten Update kommt, das am 6. passiertZweitens hat die index.jsDatei die Zählerkomponente bereits am 5. abgemeldetzweite. Wenn die Zählerkomponente ihren 6. Platz erreichtZweitens wird der Zähler zum zweiten Mal aktualisiert.

Es aktualisiert seinen Status, aber hier ist das Problem. Es gibt kein DOM für die Zählerkomponente, auf das der Status aktualisiert werden kann. In diesem Fall gibt React einen Fehler aus. Dieser schöne Fehler, den wir oben besprochen haben:

Warnung: setState (oder forceUpdate) kann für eine nicht gemountete Komponente nicht aufgerufen werden. Dies ist ein No-Op, weist jedoch auf einen Speicherverlust in Ihrer Anwendung hin. Brechen Sie zum Beheben alle Abonnements und asynchronen Aufgaben in der componentWillUnmount-Methode ab.

Wenn Sie neu bei React sind, könnten Sie sagen: „Nun, Adeel… ja, aber haben wir die Counter-Komponente nicht einfach in der 5. Sekunde abmontiert? Wenn es keine Komponente für den Zähler gibt, wie kann der Status in der sechsten Sekunde noch aktualisiert werden? “

Ja, du hast recht. Wenn wir jedoch so etwas wie setTimeoutoder setIntervalin unseren React-Komponenten ausführen, hängt dies nicht von unserer React-Klasse ab oder ist mit dieser verknüpft, wie Sie vielleicht glauben. Es läuft nach dem angegebenen Zustand weiter, es sei denn oder bis Sie das Abonnement kündigen.

Jetzt tun Sie dies möglicherweise bereits, wenn Ihre Bedingung erfüllt ist. Was aber, wenn Ihre Bedingung noch nicht erfüllt ist und der Benutzer beschließt, die Seiten zu wechseln, auf denen diese Aktion noch ausgeführt wird?

Der beste Weg, um diese Art von Abonnements zu löschen, ist in Ihrem componentWillUnmountLebenszyklus. Hier ist ein Beispiel, wie Sie es tun können. Überprüfen Sie die componentWillUnmount-Methode der Datei counter.js:

Und das ist so ziemlich alles für setTimout& setInterval.

2: API (XHR) wird abgebrochen

  • Der hässliche alte Ansatz (veraltet)
  • Der gute neuere Ansatz (Der Hauptzweck dieses Artikels)

So, we’ve discussed subscriptions. But what if you make an asynchronous request? How do you cancel it?

The old way

Before I talk about that, I want to talk about a deprecated method in React called isMounted()

Before December 2015, there was a method called isMounted in React. You can read more about it in the React blog. What it did was something like this:

import React from 'react' import ReactDOM from 'react-dom' import axios from 'axios' class RandomUser extends React.Component { state = {user: null} _isMounted = false handleButtonClick = async () => { const response = await axios.get('//randomuser.me/api/') if (this._isMounted) { this.setState({ user: response.data }) } } componentDidMount() { this._isMounted = true } componentWillUnmount() { this._isMounted = false } render() { return ( Click Me 
{JSON.stringify(this.state.user, null, 2)}
) } }

For the purpose of this example, I am using a library called axios for making an XHR request.

Let’s go through it. I initially set this_isMounted to false right next to where I initialized my state. As soon as the life cycle componentDidMount gets called, I set this._isMounted to true. During that time, if an end user clicks the button, an XHR request is made. I am using randomuser.me. As soon as the promise gets resolved, I check if the component is still mounted with this_isMounted. If it’s true, I update my state, otherwise I ignore it.

The user might clicked on the button while the asynchronous call was being resolved. This would result in the user switching pages. So to avoid an unnecessary state update, we can simply handle it in our life cycle method componentWillUnmount. I simply set this._isMounted to false. So whenever the asynchronous API call gets resolved, it will check if this_isMounted is false and then it will not update the state.

Dieser Ansatz erledigt die Arbeit, aber wie in den React-Dokumenten heißt es:

Der Hauptanwendungsfall isMounted()besteht darin, das Aufrufen setState()nach dem Aufheben der Bereitstellung einer Komponente zu vermeiden , da beim Aufrufen setState()nach dem Aufheben der Bereitstellung einer Komponente eine Warnung ausgegeben wird. Die "setState-Warnung" hilft Ihnen dabei, Fehler zu erkennen, da das Aufrufen setState()einer nicht gemounteten Komponente ein Hinweis darauf ist, dass Ihre App / Komponente nicht ordnungsgemäß bereinigt wurde. Insbesondere setState()bedeutet das Aufrufen einer nicht gemounteten Komponente, dass Ihre App nach dem Aushängen der Komponente immer noch einen Verweis auf die Komponente enthält - was häufig auf einen Speicherverlust hinweist! Weiterlesen …

Dies bedeutet, dass wir zwar einen unnötigen setState vermieden haben, der Speicher jedoch immer noch nicht gelöscht wurde. Es findet immer noch eine asynchrone Aktion statt, die nicht weiß, dass der Komponentenlebenszyklus beendet wurde und nicht mehr benötigt wird.

Sprechen wir über den richtigen Weg

Hier, um den Tag zu retten, sind AbortController . In der MDN-Dokumentation heißt es:

Die AbortControllerSchnittstelle stellt ein Controller-Objekt dar, mit dem Sie eine oder mehrere DOM-Anforderungen nach Bedarf abbrechen können. Weiterlesen ..

Schauen wir uns hier etwas genauer an. Mit Code natürlich, weil jeder ❤ Code.

var myController = new AbortController(); var mySignal = myController.signal; var downloadBtn = document.querySelector('.download'); var abortBtn = document.querySelector('.abort'); downloadBtn.addEventListener('click', fetchVideo); abortBtn.addEventListener('click', function() { myController.abort(); console.log('Download aborted'); }); function fetchVideo() { ... fetch(url, { signal: mySignal }).then(function(response) { ... }).catch(function(e) { reports.textContent = 'Download error: ' + e.message; }) } 

First we create a new AbortController and assign it to a variable called myController. Then we make a signal for that AbortController. Think of the signal as an indicator to tell our XHR requests when it’s time to abort the request.

Assume that we have 2 buttons, Download and Abort . The download button downloads a video, but what if, while downloading, we want to cancel that download request? We simply need to call myController.abort(). Now this controller will abort all requests associated with it.

How, you might ask?

After we did var myController = new AbortController() we did this var mySignal = myController.signal . Now in my fetch request, where I tell it the URL and the payload, I just need to pass in mySignal to link/signal that FETCh request with my awesome AbortController.

Wenn Sie ein noch ausführlicheres Beispiel darüber lesen möchten AbortController, haben die coolen Leute bei MDN dieses wirklich schöne und elegante Beispiel auf ihrem Github. Sie können es hier überprüfen.

Ich wollte über diese Abbruchanfragen sprechen, weil nicht viele Leute davon wissen. Die Anfrage für einen Abbruch beim Abrufen begann im Jahr 2015. Hier ist die ursprüngliche GitHub-Ausgabe zum Abbruch - sie wurde schließlich im Oktober 2017 unterstützt. Das ist eine Lücke von zwei Jahren. Beeindruckend! Es gibt einige Bibliotheken wie Axios , die AbortController unterstützen. Ich werde diskutieren, wie Sie es mit Axios verwenden können, aber ich wollte zuerst die ausführliche Version der Funktionsweise von AbortController unter der Haube zeigen.

Abbrechen einer XHR-Anfrage in Axios

"Tun oder nicht tun. Es gibt keinen Versuch. " - Yoda

The implementation I talked about above isn’t specific to React, but that’s what we’ll discuss here. The main purpose of this article is to show you how to clear unnecessary DOM manipulations in React when an XHR request is made and the component is unmounted while the request is in pending state. Whew!

So without further ado, here we go.

import React, { Component } from 'react'; import axios from 'axios'; class Example extends Component { signal = axios.CancelToken.source(); state = { isLoading: false, user: {}, } componentDidMount() { this.onLoadUser(); } componentWillUnmount() { this.signal.cancel('Api is being canceled'); } onLoadUser = async () => { try { this.setState({ isLoading: true }); const response = await axios.get('//randomuser.me/api/', { cancelToken: this.signal.token, }) this.setState({ user: response.data, isLoading: true }); } catch (err) { if (axios.isCancel(err)) { console.log('Error: ', err.message); // => prints: Api is being canceled } else { this.setState({ isLoading: false }); } } } render() { return ( 
{JSON.stringify(this.state.user, null, 2)}
) } }

Let’s walk through this code

I set this.signal to axios.CancelToken.source()which basically instantiates a new AbortController and assigns the signal of that AbortController to this.signal. Next I call a method in componentDidMount called this.onLoadUser() which calls a random user information from a third party API randomuser.me. When I call that API, I also pass the signal to a property in axios called cancelToken

The next thing I do is in my componentWillUnmount where I call the abort method which is linked to that signal. Now let’s assume that as soon as the component was loaded, the API was called and the XHR request went in a pending state.

Now, the request was pending (that is, it wasn’t resolved or rejected but the user decided to go to another page. As soon as the life cycle method componentWillUnmount gets called up, we will abort our API request. As soon as the API get’s aborted/cancelled, the promise will get rejected and it will land in the catch block of that try/catch statement, particularly in the if (axios.isCancel(err) {} block.

Now we know explicitly that the API was aborted, because the component was unmounted and therefore logs an error. But we know that we no longer need to update that state since it is no longer required.

P.S: You can use the same signal and pass it as many XHR requests in your component as you like. When the component gets un mounted, all those XHR requests that are in a pending state will get cancelled when componentWillUnmount is called.

Final details

Congratulations! :) If you have read this far, you’ve just learned how to abort an XHR request on your own terms.

Let’s carry on just a little bit more. Normally, your XHR requests are in one file, and your main container component is in another (from which you call that API method). How do you pass that signal to another file and still get that XHR request cancelled?

Here is how you do it:

import React, { Component } from 'react'; import axios from 'axios'; // API import { onLoadUser } from './UserAPI'; class Example extends Component { signal = axios.CancelToken.source(); state = { isLoading: false, user: {}, } componentDidMount() { this.onLoadUser(); } componentWillUnmount() { this.signal.cancel('Api is being canceled'); } onLoadUser = async () => { try { this.setState({ isLoading: true }); const data = await onLoadUser(this.signal.token); this.setState({ user: data, isLoading: true }); } catch (error) { if (axios.isCancel(err)) { console.log('Error: ', err.message); // => prints: Api is being canceled } else { this.setState({ isLoading: false }); } } } render() { return ( 
{JSON.stringify(this.state.user, null, 2)}
) } }; }
export const onLoadUser = async myCancelToken => { try { const { data } = await axios.get('//randomuser.me/api/', { cancelToken: myCancelToken, }) return data; } catch (error) { throw error; } }; 

I hope this has helped you and I hope you’ve learned something. If you liked it, please give it some claps.

Vielen Dank, dass Sie sich die Zeit zum Lesen genommen haben. Wenden Sie sich an meinen sehr talentierten Kollegen Kinan, der mir beim Korrekturlesen dieses Artikels geholfen hat. Vielen Dank an Kent C Dodds für seine Inspiration in der JavaScript OSS-Community.

Auch hier würde ich gerne Ihr Feedback dazu hören. Sie können mich jederzeit auf Twitter erreichen .

Es gibt auch eine weitere erstaunliche Lektüre über Abort Controller , die ich in der MDN- Dokumentation von Jake Archibald gefunden habe . Ich schlage vor, Sie lesen es, wenn Sie eine kuriose Natur wie meine haben.