L'utilisation d'Async componentDidMount () est-elle bonne?

139

L'utilisation en componentDidMount()tant que fonction asynchrone est-elle une bonne pratique dans React Native ou dois-je l'éviter?

J'ai besoin d'obtenir des informations sur AsyncStoragele montage du composant, mais le seul moyen que je connaisse pour rendre cela possible est de rendre la componentDidMount()fonction asynchrone.

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

Y a-t-il un problème avec cela et y a-t-il d'autres solutions à ce problème?

Mirakurun
la source
2
La «bonne pratique» est une question d’opinion. Est-ce que ça marche? Oui.
Kraylog
2
Voici un bon article qui montre pourquoi l'attente asynchrone est une bonne option par rapport aux promesses hackernoon.com
...
il suffit d'utiliser redux-thunk, cela résoudra le problème
Tilak Maddy
@TilakMaddy Pourquoi supposez-vous que chaque application de réaction utilise redux?
Mirakurun le
@Mirakurun pourquoi tout le débordement de pile a-t-il supposé que j'utilisais jQuery alors que j'avais l'habitude de poser des questions javascript simples dans la journée?
Tilak Maddy

Réponses:

162

Commençons par souligner les différences et déterminer comment cela pourrait causer des problèmes.

Voici le code de la componentDidMount()méthode du cycle de vie async et "sync" :

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

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

En regardant le code, je peux souligner les différences suivantes:

  1. Les asyncmots-clés: en texte dactylographié, il ne s'agit que d'un marqueur de code. Il fait 2 choses:
    • Forcer le type de retour à être Promise<void>au lieu de void. Si vous spécifiez explicitement que le type de retour n'est pas promis (ex: void), le tapuscript vous crachera une erreur.
    • Vous permet d'utiliser des awaitmots-clés dans la méthode.
  2. Le type de retour passe de voidàPromise<void>
    • Cela signifie que vous pouvez maintenant faire ceci:
      async someMethod(): Promise<void> { await componentDidMount(); }
  3. Vous pouvez maintenant utiliser le awaitmot-clé dans la méthode et suspendre temporairement son exécution. Comme ça:

    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();
    }

Maintenant, comment pourraient-ils causer des problèmes?

  1. Le asyncmot-clé est absolument inoffensif.
  2. Je ne peux imaginer aucune situation dans laquelle vous devez effectuer un appel à la componentDidMount()méthode afin que le type de retour Promise<void>soit également inoffensif.

    L'appel à une méthode dont le type de retour est Promise<void>sans awaitmot-clé ne fera aucune différence par rapport à l'appel d'une méthode dont le type de retour est void.

  3. Puisqu'il n'y a pas de méthode de cycle de vie après avoir componentDidMount()retardé son exécution, cela semble assez sûr. Mais il y a un piège.

    Disons que ce qui précède this.setState({users, questions});serait exécuté après 10 secondes. Au milieu du temps d'attente, un autre ...

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

    ... ont été exécutés avec succès et le DOM a été mis à jour. Le résultat était visible pour les utilisateurs. L'horloge a continué à tourner et 10 secondes se sont écoulées. Le retard this.setState(...)s'exécuterait alors et le DOM serait à nouveau mis à jour, cette fois avec d'anciens utilisateurs et d'anciennes questions. Le résultat serait également visible pour les utilisateurs.

=> Il est assez sûr (je ne suis pas sûr à 100%) à utiliser asyncavec la componentDidMount()méthode. J'en suis un grand fan et jusqu'à présent je n'ai rencontré aucun problème qui me donne trop de maux de tête.

Cù Đức Hiếu
la source
Lorsque vous parlez du problème où un autre setState s'est produit avant une promesse en attente, n'est-ce pas la même chose avec Promise sans le sucre syntaxique async / await ou même les rappels classiques?
Clafou
3
Oui! Retarder un setState()comporte toujours un petit risque. Nous devons procéder avec prudence.
Cù Đức Hiếu
Je suppose qu'une manière d'éviter les problèmes est d'utiliser quelque chose comme à l' isFetching: trueintérieur de l'état d'un composant. Je n'ai utilisé cela qu'avec redux, mais je suppose que c'est tout à fait valide avec la gestion de l'état de réaction uniquement. Bien que cela ne résout pas vraiment le problème de la mise à jour du même état ailleurs dans le code ...
Clafou
1
Je suis d'accord avec ça. En fait, la isFetchingsolution de drapeau est assez courante surtout lorsque nous voulons jouer certaines animations en front-end en attendant la réponse du back-end ( isFetching: true).
Cù Đức Hiếu
3
Vous pouvez rencontrer des problèmes si vous faites setState après le démontage du composant
Eliezer Steinbock
18

Mise à jour d'avril 2020: Le problème semble être résolu dans la dernière React 16.13.1, voir cet exemple de bac à sable . Merci à @abernier pour l'avoir signalé.


J'ai fait quelques recherches et j'ai trouvé une différence importante: React ne traite pas les erreurs des méthodes de cycle de vie asynchrones.

Donc, si vous écrivez quelque chose comme ceci:

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

alors votre erreur sera interceptée par la limite d'erreur , et vous pouvez la traiter et afficher un message gracieux.

Si nous changeons le code comme ceci:

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

ce qui équivaut à ceci:

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

alors votre erreur sera avalée en silence . Honte à toi, réagis ...

Alors, comment traitons-nous les erreurs? Le seul moyen semble être une capture explicite comme celle-ci:

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

ou comme ça:

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

Si nous voulons toujours que notre erreur atteigne la limite d'erreur, je peux penser à l'astuce suivante:

  1. Attrapez l'erreur, faites changer l'état du composant par le gestionnaire d'erreurs
  2. Si l'état indique une erreur, supprimez-la de la renderméthode

Exemple:

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>;
  }
}
CF
la source
y a-t-il un problème signalé pour cela? Pourrait être utile de le signaler si toujours le cas ... thx
abernier
@abernier Je pense que c'est par digne ... Bien qu'ils puissent probablement l'améliorer. Je n'ai signalé aucun problème à ce sujet ...
CF le
1
cela ne semble plus être le cas, du moins avec React 16.13.1 testé ici: codesandbox.io/s/bold-ellis-n1cid?file=/src/App.js
abernier
9

Votre code est bon et très lisible pour moi. Voir cet article de Dale Jefferson où il montre un componentDidMountexemple asynchrone et a l'air vraiment bien aussi.

Mais certaines personnes diraient qu'une personne lisant le code peut supposer que React fait quelque chose avec la promesse retournée.

Donc, l'interprétation de ce code et s'il s'agit d'une bonne pratique ou non est très personnelle.

Si vous voulez une autre solution, vous pouvez utiliser des promesses . Par exemple:

componentDidMount() {
    fetch(this.getAuth())
      .then(auth => {
          if (auth) this.checkAuth(auth)
      })
}
Tiago Alves
la source
3
... ou aussi, utilisez simplement une asyncfonction en ligne avec awaits à l'intérieur ...?
Erik Kaplun
aussi une option @ErikAllik :)
Tiago Alves
@ErikAllik avez-vous un exemple?
Pablo Rincon
1
@PabloRincon smth comme (async () => { const data = await fetch('foo'); const result = await submitRequest({data}); console.log(result) })()where fetchet submitRequestsont des fonctions qui renvoient des promesses.
Erik Kaplun
Ce code est définitivement mauvais, car il avalera toute erreur survenue dans la fonction getAuth. Et si la fonction fait quelque chose avec le réseau (par exemple), des erreurs doivent être attendues.
CF le
6

Lorsque vous utilisez componentDidMountsans asyncmot-clé, le doc dit ceci:

Vous pouvez appeler setState () immédiatement dans componentDidMount (). Cela déclenchera un rendu supplémentaire, mais cela se produira avant que le navigateur ne mette à jour l'écran.

Si vous utilisez, async componentDidMountvous perdrez cette capacité: un autre rendu se produira APRÈS la mise à jour de l'écran par le navigateur. Mais imo, si vous songez à utiliser async, comme la récupération de données, vous ne pouvez pas éviter que le navigateur mette à jour l'écran deux fois. Dans un autre monde, il n'est pas possible de PAUSE componentDidMount avant que le navigateur ne mette à jour l'écran

Lu Tran
la source
1
J'aime cette réponse car elle est concise et soutenue par des documents. Pouvez-vous s'il vous plaît ajouter un lien vers les documents que vous référencez.
theUtherSide
Cela peut même être une bonne chose, par exemple si vous affichez un état de chargement pendant le chargement de la ressource, puis le contenu une fois terminé.
Hjulle
3

Mettre à jour:

(Ma version: React 16, Webpack 4, Babel 7):

En utilisant Babel 7, vous découvrirez:

Utilisation de ce modèle ...

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

vous rencontrerez l'erreur suivante ...

Uncaught ReferenceError: regeneratorRuntime n'est pas défini

Dans ce cas, vous devrez installer babel-plugin-transform-runtime

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

Si pour une raison quelconque vous ne souhaitez pas installer le package ci-dessus (babel-plugin-transform-runtime), vous voudrez vous en tenir au modèle Promise ...

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

Je pense que ça va tant que tu sais ce que tu fais. Mais cela peut être déroutant car il async componentDidMount()peut toujours être en cours d'exécution après l' componentWillUnmountexécution et le démontage du composant.

Vous pouvez également démarrer des tâches synchrones et asynchrones à l'intérieur componentDidMount. Si componentDidMountétait asynchrone, vous auriez à mettre tout le code synchrone avant le premier await. Il n'est peut-être pas évident pour quelqu'un que le code avant le premier awaits'exécute de manière synchrone. Dans ce cas, je resterais probablement componentDidMountsynchrone, mais je lui ferais appeler les méthodes de synchronisation et d'asynchronisation.

Que vous choisissiez les méthodes d' appel async componentDidMount()vs sync , vous devez vous assurer de nettoyer tous les écouteurs ou méthodes asynchrones qui peuvent encore être en cours d'exécution lorsque le composant est démonté.componentDidMount()async

dosentmatter
la source
2

En fait, le chargement asynchrone dans ComponentDidMount est un modèle de conception recommandé car React s'éloigne des méthodes de cycle de vie héritées (componentWillMount, componentWillReceiveProps, componentWillUpdate) et passe au rendu asynchrone.

Cet article de blog est très utile pour expliquer pourquoi cela est sûr et fournir des exemples de chargement asynchrone dans ComponentDidMount:

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

DannyMoshe
la source
3
Le rendu asynchrone n'a en fait rien à voir avec le fait de rendre le cycle de vie explicitement asynchrone. C'est en fait un anti-modèle. La solution recommandée est d'appeler une méthode asynchrone à partir d'une méthode de cycle de vie
Clayton Ray
1

J'aime utiliser quelque chose comme ça

componentDidMount(){
   const result = makeResquest()
}
async makeRequest(){
   const res = await fetch(url);
   const data = await res.json();
   return data
}
Gustavo Miguel
la source