Ist die Verwendung von async componentDidMount () gut?


137

Ist die Verwendung componentDidMount()als asynchrone Funktion in React Native eine gute Praxis oder sollte ich sie vermeiden?

Ich muss einige Informationen AsyncStorageabrufen, wenn die Komponente bereitgestellt wird, aber der einzige Weg, den ich kenne, um dies zu ermöglichen, besteht darin, die componentDidMount()Funktion asynchron zu machen .

async componentDidMount() {
    let auth = await this.getAuth();
    if (auth) 
        this.checkAuth(auth);
}

Gibt es ein Problem damit und gibt es andere Lösungen für dieses Problem?


1
"Gute Praxis" ist Ansichtssache. Funktioniert es? Ja.
Kraylog

2
Hier ist ein guter Artikel, der zeigt, warum asynchrones
Warten

Verwenden Sie einfach Redux-Thunk, um das Problem zu lösen
Tilak Maddy

@ TilakMaddy Warum nimmst du an, dass jede Reaktions-App Redux verwendet?
Mirakurun

@Mirakurun, warum ging der gesamte Stapelüberlauf davon aus, dass ich jQuery verwende, als ich früher einfache Javascript-Fragen stellte?
Tilak Maddy

Antworten:


161

Beginnen wir damit, auf die Unterschiede hinzuweisen und festzustellen, wie dies zu Problemen führen kann.

Hier ist der Code für die asynchrone und "synchronisierte" componentDidMount()Lebenszyklusmethode:

// This is typescript code
componentDidMount(): void { /* do something */ }

async componentDidMount(): Promise<void> {
    /* do something */
    /* You can use "await" here */
}

Wenn ich mir den Code ansehe, kann ich auf folgende Unterschiede hinweisen:

  1. Die asyncSchlüsselwörter: Im Typoskript ist dies lediglich eine Codemarkierung. Es macht 2 Dinge:
    • Erzwingen Sie den Rückgabetyp Promise<void>anstelle von void. Wenn Sie den Rückgabetyp explizit als nicht versprechend angeben (z. B. void), wird Typoskript einen Fehler an Sie ausspucken.
    • Ermöglichen die Verwendung von awaitSchlüsselwörtern innerhalb der Methode.
  2. Der Rückgabetyp wird von voidnach geändertPromise<void>
    • Dies bedeutet, dass Sie dies jetzt tun können:
      async someMethod(): Promise<void> { await componentDidMount(); }
  3. Sie können jetzt das awaitSchlüsselwort in der Methode verwenden und deren Ausführung vorübergehend anhalten. So was:

    async componentDidMount(): Promise<void> {
        const users = await axios.get<string>("http://localhost:9001/users");
        const questions = await axios.get<string>("http://localhost:9001/questions");
    
        // Sleep for 10 seconds
        await new Promise(resolve => { setTimeout(resolve, 10000); });
    
        // This line of code will be executed after 10+ seconds
        this.setState({users, questions});
        return Promise.resolve();
    }

Wie könnten sie nun Probleme verursachen?

  1. Das asyncSchlüsselwort ist absolut harmlos.
  2. Ich kann mir keine Situation vorstellen, in der Sie die componentDidMount()Methode aufrufen müssen, damit auch der Rückgabetyp Promise<void>harmlos ist.

    Das Aufrufen einer Methode mit dem Rückgabetyp Promise<void>ohne awaitSchlüsselwort macht keinen Unterschied zum Aufrufen einer Methode mit dem Rückgabetyp von void.

  3. Da es nach componentDidMount()Verzögerung der Ausführung keine Lebenszyklusmethoden gibt, scheint dies ziemlich sicher zu sein. Aber es gibt eine Gotcha.

    Angenommen, das oben genannte wird this.setState({users, questions});nach 10 Sekunden ausgeführt. Mitten in der Verzögerungszeit ...

    this.setState({users: newerUsers, questions: newerQuestions});

    ... wurden erfolgreich ausgeführt und das DOM aktualisiert. Das Ergebnis war für Benutzer sichtbar. Die Uhr tickte weiter und es vergingen 10 Sekunden. Die Verzögerung this.setState(...)würde dann ausgeführt und das DOM würde erneut aktualisiert, diesmal mit alten Benutzern und alten Fragen. Das Ergebnis wäre auch für Benutzer sichtbar.

=> Es ist ziemlich sicher (ich bin mir nicht sicher über 100%), asyncmit componentDidMount()Methode zu verwenden. Ich bin ein großer Fan davon und habe bisher keine Probleme festgestellt, die mir zu viele Kopfschmerzen bereiten.


Wenn Sie über das Problem sprechen, bei dem ein anderer setState vor einem ausstehenden Versprechen aufgetreten ist, ist das nicht dasselbe mit Versprechen ohne den asynchronen / wartenden syntaktischen Zucker oder sogar klassische Rückrufe?
Clafou

3
Ja! Eine Verzögerung ist setState()immer mit einem geringen Risiko verbunden. Wir sollten vorsichtig vorgehen.
Cù Đức Hiếu

Ich denke, eine Möglichkeit, Probleme zu vermeiden, besteht darin, so etwas wie isFetching: trueim Zustand einer Komponente zu verwenden. Ich habe dies nur mit Redux verwendet, aber ich nehme an, dass es mit der Nur-Reaktion-Statusverwaltung vollständig gültig ist. Obwohl es das Problem, dass derselbe Status irgendwo anders im Code aktualisiert wird, nicht wirklich löst ...
Clafou

1
Ich stimme dem zu. Tatsächlich ist die isFetchingFlag-Lösung ziemlich häufig, insbesondere wenn wir einige Animationen im Front-End abspielen möchten, während wir auf die Back-End-Antwort warten ( isFetching: true).
Cù Đức Hiếu

3
Sie können auf Probleme stoßen, wenn Sie setState ausführen, nachdem die Komponente abgemeldet wurde
Eliezer Steinbock

18

Update April 2020: Das Problem scheint in React 16.13.1 behoben zu sein (siehe dieses Sandbox-Beispiel) . Vielen Dank an @abernier für diesen Hinweis.


Ich habe einige Nachforschungen angestellt und einen wichtigen Unterschied festgestellt: React verarbeitet keine Fehler aus asynchronen Lebenszyklusmethoden.

Also, wenn Sie so etwas schreiben:

componentDidMount()
{
    throw new Error('I crashed!');
}

Dann wird Ihr Fehler von der Fehlergrenze erfasst , und Sie können ihn verarbeiten und eine ordnungsgemäße Meldung anzeigen.

Wenn wir den Code folgendermaßen ändern:

async componentDidMount()
{
    throw new Error('I crashed!');
}

was dem entspricht:

componentDidMount()
{
    return Promise.reject(new Error('I crashed!'));
}

dann wird Ihr Fehler lautlos verschluckt . Schande über dich, reagiere ...

Wie verarbeiten wir Fehler als? Der einzige Weg scheint ein expliziter Fang wie dieser zu sein:

async componentDidMount()
{
    try
    {
         await myAsyncFunction();
    }
    catch(error)
    {
        //...
    }
}

oder so:

componentDidMount()
{
    myAsyncFunction()
    .catch(()=>
    {
        //...
    });
}

Wenn wir immer noch wollen, dass unser Fehler die Fehlergrenze erreicht, kann ich über den folgenden Trick nachdenken:

  1. Fangen Sie den Fehler ab und lassen Sie den Fehlerbehandler den Komponentenstatus ändern
  2. Wenn der Status einen Fehler anzeigt, werfen Sie ihn aus der renderMethode

Beispiel:

class BuggyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }

  buggyAsyncfunction(){ return Promise.reject(new Error('I crashed async!'));}

  async componentDidMount() {
    try
    {
      await this.buggyAsyncfunction();
    }
    catch(error)
    {
        this.setState({error: error});
    }
  }

  render() {
    if(this.state.error)
        throw this.state.error;

    return <h1>I am OK</h1>;
  }
}

Gibt es ein Problem, das dafür gemeldet wurde? Könnte nützlich sein, um es zu melden, wenn es immer noch der Fall ist ... thx
abernier

@abernier Ich denke, es ist perign ... Obwohl sie es wahrscheinlich verbessern könnten. Ich habe keine Probleme zu diesem Thema
CF

1
Zumindest bei React 16.13.1, wie hier getestet, scheint dies nicht mehr der Fall zu sein: Codesandbox.io/s/bold-ellis-n1cid?file=/src/App.js
abernier

9

Ihr Code ist in Ordnung und für mich sehr lesbar. Sehen Sie sich diesen Artikel von Dale Jefferson an, in dem er ein asynchrones componentDidMountBeispiel zeigt und auch wirklich gut aussieht.

Aber einige Leute würden sagen, dass eine Person, die den Code liest, annehmen könnte, dass React etwas mit dem zurückgegebenen Versprechen macht.

Die Interpretation dieses Codes und ob es sich um eine gute Praxis handelt oder nicht, ist also sehr persönlich.

Wenn Sie eine andere Lösung wünschen, können Sie Versprechen verwenden . Beispielsweise:

componentDidMount() {
    fetch(this.getAuth())
      .then(auth => {
          if (auth) this.checkAuth(auth)
      })
}

3
... oder verwenden Sie einfach eine Inline- asyncFunktion mit awaits im Inneren ...?
Erik Kaplun

auch eine Option @ErikAllik :)
Tiago Alves

@ErikAllik hast du zufällig ein Beispiel?
Pablo Rincon

1
@PabloRincon smth wie (async () => { const data = await fetch('foo'); const result = await submitRequest({data}); console.log(result) })()wo fetchund submitRequestsind Funktionen, die Versprechen zurückgeben.
Erik Kaplun

Dieser Code ist definitiv schlecht, da er jeden Fehler verschluckt, der in der Funktion getAuth aufgetreten ist. Und wenn die Funktion etwas mit dem Netzwerk macht (zum Beispiel), müssen Fehler erwartet werden.
CF

6

Wenn Sie componentDidMountohne asyncSchlüsselwort verwenden, sagt das Dokument Folgendes:

Sie können setState () sofort in componentDidMount () aufrufen. Es wird ein zusätzliches Rendering ausgelöst, dies geschieht jedoch, bevor der Browser den Bildschirm aktualisiert.

Wenn Sie verwenden async componentDidMount, verlieren Sie diese Fähigkeit: Ein weiteres Rendern erfolgt, nachdem der Browser den Bildschirm aktualisiert hat. Aber imo, wenn Sie über die Verwendung von Async nachdenken, z. B. das Abrufen von Daten, können Sie nicht vermeiden, dass der Browser den Bildschirm zweimal aktualisiert. In einer anderen Welt ist es nicht möglich, componentDidMount zu PAUSE, bevor der Browser den Bildschirm aktualisiert


1
Ich mag diese Antwort, weil sie prägnant ist und von Dokumenten unterstützt wird. Können Sie bitte einen Link zu den Dokumenten hinzufügen, auf die Sie verweisen?
theUtherSide

Dies kann sogar eine gute Sache sein, z. B. wenn Sie einen Ladezustand anzeigen, während die Ressource geladen wird, und dann den Inhalt, wenn dies erledigt ist.
Hjulle

3

Aktualisieren:

(Mein Build: React 16, Webpack 4, Babel 7):

Wenn Sie Babel 7 verwenden, werden Sie feststellen:

Mit diesem Muster ...

async componentDidMount() {
    try {
        const res = await fetch(config.discover.url);
        const data = await res.json();
        console.log(data);
    } catch(e) {
        console.error(e);
    }
}

Sie werden auf den folgenden Fehler stoßen ...

Nicht erfasster ReferenceError: regeneratorRuntime ist nicht definiert

In diesem Fall müssen Sie babel-plugin-transform-runtime installieren

https://babeljs.io/docs/en/babel-plugin-transform-runtime.html

Wenn Sie aus irgendeinem Grund das obige Paket (babel-plugin-transform-runtime) nicht installieren möchten, sollten Sie sich an das Promise-Muster halten ...

componentDidMount() {
    fetch(config.discover.url)
    .then(res => res.json())
    .then(data => {
        console.log(data);
    })
    .catch(err => console.error(err));
}

3

Ich denke, es ist in Ordnung, solange Sie wissen, was Sie tun. Dies kann jedoch verwirrend sein, da async componentDidMount()es nach dem componentWillUnmountAusführen und dem Abmelden der Komponente weiterhin ausgeführt werden kann .

Möglicherweise möchten Sie auch sowohl synchrone als auch asynchrone Aufgaben im Inneren starten componentDidMount. Wenn componentDidMountasynchron wäre, müssten Sie den gesamten synchronen Code vor dem ersten setzen await. Für jemanden ist es möglicherweise nicht offensichtlich, dass der Code vor dem ersten awaitsynchron ausgeführt wird. In diesem Fall würde ich wahrscheinlich componentDidMountsynchron bleiben, aber Sync- und Async-Methoden aufrufen lassen.

Ob Sie async componentDidMount()vs sync componentDidMount()aufrufen asyncMethoden, müssen Sie sicherstellen , dass aufzuräumen Sie Hörer oder Asynchron - Methoden , die noch ausgeführt werden, wenn die Komponente Aushänge.


2

Tatsächlich ist das asynchrone Laden in ComponentDidMount ein empfohlenes Entwurfsmuster, da React von älteren Lebenszyklusmethoden (componentWillMount, componentWillReceiveProps, componentWillUpdate) zum asynchronen Rendern übergeht.

Dieser Blog-Beitrag ist sehr hilfreich, um zu erklären, warum dies sicher ist, und um Beispiele für das asynchrone Laden in ComponentDidMount bereitzustellen:

https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html


2
Asynchrones Rendern hat eigentlich nichts damit zu tun, den Lebenszyklus explizit asynchron zu machen. Es ist eigentlich ein Anti-Muster. Die empfohlene Lösung besteht darin, eine asynchrone Methode aus einer Lebenszyklusmethode aufzurufen
Clayton Ray,

1

Ich benutze so etwas gerne

componentDidMount(){
   const result = makeResquest()
}
async makeRequest(){
   const res = await fetch(url);
   const data = await res.json();
   return data
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.