239 votes

React - Afficher un écran de chargement pendant le rendu du DOM ?

Voici un exemple tiré de la page de demande de Google Adsense. L'écran de chargement s'est affiché avant que la page principale ne s'affiche après.

enter image description here

Je ne sais pas comment faire la même chose avec React car si je fais en sorte que l'écran de chargement soit rendu par un composant React, il ne s'affiche pas pendant le chargement de la page car il doit attendre le rendu du DOM avant.

Mise à jour de :

J'ai fait un exemple de mon approche en mettant le chargeur d'écran dans index.html et le supprimer dans React componentDidMount() la méthode du cycle de vie.

Exemple et react-loading-screen .

1 votes

Montrez ce que vous voulez montrer en js simple, puis rendez-le caché ou supprimez-le du DOM lorsque la réaction est montée. Tout ce que vous avez à faire est de le cacher du code de react.

2 votes

C'est tout simplement merveilleux ! Merci.

2 votes

Je suis d'accord pour dire que c'est une approche merveilleuse. J'ai expédié plusieurs applications react où j'ai mis l'écran de chargement à l'intérieur de <div id="Root"> </div> (ce qui fonctionne) mais il peut y avoir un "écran blanc" de courte durée entre le premier appel de ReactDOM.render() et le moment où le composant serait effectivement peint. L'utilisation d'un positionnement fixe pour l'écran de chargement, puis du crochet componentDidUpdate (ou useEffect) avec CSS pour l'estomper puis le supprimer complètement est merveilleuse. Cela garantit que vous ne supprimez pas l'écran de chargement avant que votre composant react entièrement peint ne soit déjà en dessous, prêt à être visualisé.

256voto

Ori Drori Points 65611

L'objectif

Lorsque la page html est rendue, afficher un spinner immédiatement (pendant le chargement de React), et le cacher une fois que React est prêt.

Puisque le spinner est rendu en HTML/CSS pur (en dehors du domaine React), React ne devrait pas contrôler directement le processus d'affichage et de masquage, et l'implémentation devrait être transparente pour React.

Solution 1 - la pseudo-classe :empty

Puisque vous rendez react dans un conteneur DOM - <div id="app"></div> vous pouvez ajouter un spinner à ce conteneur, et lorsque react se charge et effectue le rendu, le spinner disparaît.

Vous ne pouvez pas ajouter un élément DOM (un div par exemple) à l'intérieur de react Root, car React remplacera le contenu du conteneur dès que ReactDOM.render() est appelé. Même si vous rendez null le contenu serait quand même remplacé par un commentaire. <!-- react-empty: 1 --> . Cela signifie que si vous souhaitez afficher le chargeur pendant que le composant principal se monte, que les données se chargent, mais que rien n'est réellement rendu, une balise de chargeur placée à l'intérieur du conteneur ( <div id="app"><div class="loader"></div></div> par exemple) ne fonctionnerait pas.

Une solution de contournement consiste à ajouter la classe spinner au conteneur react, et à utiliser l'attribut :empty pseudo classe . Le spinner sera visible, tant que rien n'est rendu dans le conteneur (les commentaires ne comptent pas). Dès que react rendra autre chose qu'un commentaire, le chargeur disparaîtra.

Exemple 1

Dans l'exemple, vous pouvez voir un composant qui restitue null jusqu'à ce qu'il soit prêt. Le conteneur est aussi le chargeur - <div id="app" class="app"></div> et la classe du chargeur ne fonctionnera que si elle est :empty (voir les commentaires dans le code) :

class App extends React.Component {
  state = {
    loading: true
  };

  componentDidMount() {
    // this simulates an async action, after which the component will render the content
    demoAsyncCall().then(() => this.setState({ loading: false }));
  }

  render() {
    const { loading } = this.state;

    if(loading) { // if your component doesn't have to wait for an async action, remove this block 
      return null; // render null when app is not ready
    }

    return (
      <div>I'm the app</div>
    ); 
  }
}

function demoAsyncCall() {
  return new Promise((resolve) => setTimeout(() => resolve(), 2500));
}

ReactDOM.render(
  <App />,
  document.getElementById('app')
);

.loader:empty {
  position: absolute;
  top: calc(50% - 4em);
  left: calc(50% - 4em);
  width: 6em;
  height: 6em;
  border: 1.1em solid rgba(0, 0, 0, 0.2);
  border-left: 1.1em solid #000000;
  border-radius: 50%;
  animation: load8 1.1s infinite linear;
}

@keyframes load8 {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>

<div id="app" class="loader"></div> <!-- add class loader to container -->

Exemple 2

Une variante de l'utilisation de la :empty pour afficher/masquer un sélecteur, c'est de faire du spinner un élément frère du conteneur de l'application et de l'afficher tant que le conteneur est vide à l'aide de la pseudo-classe Combinateur de frères et sœurs adjacents ( + ) :

class App extends React.Component {
  state = {
    loading: true
  };

  componentDidMount() {
    // this simulates an async action, after which the component will render the content
    demoAsyncCall().then(() => this.setState({ loading: false }));
  }

  render() {
    const { loading } = this.state;

    if(loading) { // if your component doesn't have to wait for async data, remove this block 
      return null; // render null when app is not ready
    }

    return (
      <div>I'm the app</div>
    ); 
  }
}

function demoAsyncCall() {
  return new Promise((resolve) => setTimeout(() => resolve(), 2500));
}

ReactDOM.render(
  <App />,
  document.getElementById('app')
);

#app:not(:empty) + .sk-cube-grid {
  display: none;
}

.sk-cube-grid {
  width: 40px;
  height: 40px;
  margin: 100px auto;
}

.sk-cube-grid .sk-cube {
  width: 33%;
  height: 33%;
  background-color: #333;
  float: left;
  animation: sk-cubeGridScaleDelay 1.3s infinite ease-in-out;
}

.sk-cube-grid .sk-cube1 {
  animation-delay: 0.2s;
}

.sk-cube-grid .sk-cube2 {
  animation-delay: 0.3s;
}

.sk-cube-grid .sk-cube3 {
  animation-delay: 0.4s;
}

.sk-cube-grid .sk-cube4 {
  animation-delay: 0.1s;
}

.sk-cube-grid .sk-cube5 {
  animation-delay: 0.2s;
}

.sk-cube-grid .sk-cube6 {
  animation-delay: 0.3s;
}

.sk-cube-grid .sk-cube7 {
  animation-delay: 0s;
}

.sk-cube-grid .sk-cube8 {
  animation-delay: 0.1s;
}

.sk-cube-grid .sk-cube9 {
  animation-delay: 0.2s;
}

@keyframes sk-cubeGridScaleDelay {
  0%,
  70%,
  100% {
    transform: scale3D(1, 1, 1);
  }
  35% {
    transform: scale3D(0, 0, 1);
  }
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>

<div id="app"></div>
<!-- add class loader to container -->

<div class="sk-cube-grid">
  <div class="sk-cube sk-cube1"></div>
  <div class="sk-cube sk-cube2"></div>
  <div class="sk-cube sk-cube3"></div>
  <div class="sk-cube sk-cube4"></div>
  <div class="sk-cube sk-cube5"></div>
  <div class="sk-cube sk-cube6"></div>
  <div class="sk-cube sk-cube7"></div>
  <div class="sk-cube sk-cube8"></div>
  <div class="sk-cube sk-cube9"></div>
</div>

Solution 2 - Passer les "handlers" de spinner comme props

Pour avoir un contrôle plus fin sur l'état d'affichage des spinners, créez deux fonctions showSpinner et hideSpinner et les transmettre au conteneur Root via les props. Les fonctions peuvent manipuler le DOM, ou faire tout ce qui est nécessaire pour contrôler le spinner. De cette façon, React n'est pas conscient du "monde extérieur" et n'a pas besoin de contrôler le DOM directement. Vous pouvez facilement remplacer les fonctions pour les tests, ou si vous devez changer la logique, et vous pouvez les passer à d'autres composants dans l'arbre React.

Exemple 1

const loader = document.querySelector('.loader');

// if you want to show the loader when React loads data again
const showLoader = () => loader.classList.remove('loader--hide');

const hideLoader = () => loader.classList.add('loader--hide');

class App extends React.Component {
  componentDidMount() {
    this.props.hideLoader();
  }

  render() {   
    return (
      <div>I'm the app</div>
    ); 
  }
}

// the setTimeout simulates the time it takes react to load, and is not part of the solution
setTimeout(() => 
  // the show/hide functions are passed as props
  ReactDOM.render(
    <App
      hideLoader={hideLoader}
      showLoader={showLoader} 
      />,
    document.getElementById('app')
  )
, 1000);

.loader {
  position: absolute;
  top: calc(50% - 4em);
  left: calc(50% - 4em);
  width: 6em;
  height: 6em;
  border: 1.1em solid rgba(0, 0, 0, 0.2);
  border-left: 1.1em solid #000000;
  border-radius: 50%;
  animation: load8 1.1s infinite linear;
  transition: opacity 0.3s;
}

.loader--hide {
  opacity: 0;
}

@keyframes load8 {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>

<div id="app"></div>

<div class="loader"></div>

Exemple 2 - crochets

Cet exemple utilise le useEffect pour masquer le spinner après le montage du composant.

const { useEffect } = React;

const loader = document.querySelector('.loader');

// if you want to show the loader when React loads data again
const showLoader = () => loader.classList.remove('loader--hide');

const hideLoader = () => loader.classList.add('loader--hide');

const App = ({ hideLoader }) => {
  useEffect(hideLoader, []);

  return (
    <div>I'm the app</div>
  ); 
}

// the setTimeout simulates the time it takes react to load, and is not part of the solution
setTimeout(() => 
  // the show/hide functions are passed as props
  ReactDOM.render(
    <App
      hideLoader={hideLoader}
      showLoader={showLoader} 
      />,
    document.getElementById('app')
  )
, 1000);

.loader {
  position: absolute;
  top: calc(50% - 4em);
  left: calc(50% - 4em);
  width: 6em;
  height: 6em;
  border: 1.1em solid rgba(0, 0, 0, 0.2);
  border-left: 1.1em solid #000000;
  border-radius: 50%;
  animation: load8 1.1s infinite linear;
  transition: opacity 0.3s;
}

.loader--hide {
  opacity: 0;
}

@keyframes load8 {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="app"></div>

<div class="loader"></div>

0 votes

Pourriez-vous préciser où se trouvent les deux dernières sections du code ? La première est clairement dans le fichier javascript src pour le composant react, la troisième, je suppose, va dans le template html qui sera rendu par ledit fichier js, mais où va la seconde ?

1 votes

Le second est le CSS. J'ai utilisé le CSS global, mais vous pouvez utiliser les modules CSS ou le CSS en JS. Le 3ème est le fichier HTML, qui peut inclure des balises de spinner si nécessaire (2ème exemple).

0 votes

Ce délai n'est pas bon si l'on tient compte des performances.

145voto

Khang Points 4457

Pour ce faire, vous pouvez placer l'icône de chargement dans votre fichier html (index.html par exemple), de sorte que les utilisateurs voient l'icône immédiatement après le chargement du fichier html.

Lorsque le chargement de votre application est terminé, vous pouvez simplement supprimer l'icône de chargement dans un crochet de cycle de vie. componentDidMount .

33 votes

Si vous montez le composant Root sur le nœud parent de cette icône, il n'y a même pas besoin de le supprimer manuellement. React nettoiera les enfants du noeud de montage et placera son propre DOM nouvellement rendu à la place.

6 votes

Je ne mets pas l'icône dans le nœud racine de l'application React, cela ne me semble pas correct.

3 votes

Y a-t-il un inconvénient à cela pour les PWA ? Cela va-t-il interférer avec l'écran d'accueil par défaut ?

57voto

Amoolya S Kumar Points 844

La solution de contournement est la suivante :

Dans votre fonction de rendu, faites quelque chose comme ceci :

constructor() {
    this.state = { isLoading: true }
}

componentDidMount() {
    this.setState({isLoading: false})
}

render() {
    return(
        this.state.isLoading ? *showLoadingScreen* : *yourPage()*
    )
}

Initialiser isLoading comme true dans le constructeur et false sur componentDidMount

0 votes

Lorsque nous avons appelé la méthode ajax pour charger les données dans les composants enfants, componentDidMount est appelé avant que les données des composants enfants ne soient remplies. Comment surmonter ce genre de problème ?

3 votes

Pour le cycle de vie du montage, c'est bien, voulez-vous ajouter quelque chose pour le cycle de vie de la mise à jour ?

0 votes

Dois-je le faire dans toutes les pages ou seulement dans l'entrée de l'application ?

22voto

Hafiz Ismail Points 2808

Si quelqu'un cherche une bibliothèque prête à l'emploi, sans configuration et sans dépendances pour le cas d'utilisation ci-dessus, essayez pace.js ( https://codebyzach.github.io/pace/docs/ ).

Il s'accroche automatiquement aux événements (ajax, readyState, history pushstate, js event loop etc) et affiche un chargeur personnalisable.

Fonctionne bien avec nos projets react/relay (gère les changements de navigation à l'aide de react-router, relaie les demandes). (Pas d'affiliation ; nous avons utilisé pace.js pour nos projets et cela a très bien fonctionné).

1 votes

Hey ! Pouvez-vous me dire comment l'utiliser avec react ?

1 votes

Il suffit d'attacher le script à public/index.html et choisir un style. c'est très simple, un plugin étonnant. merci.

1 votes

Je n'aurais pas trouvé le rythme sans cette réponse. Il était si facile de l'inclure, et avec un peu de magie CSS et quelques pièces jointes d'événements, j'ai pu bloquer/désactiver l'application pendant les transitions et personnaliser le spinner.

14voto

rm- Points 4069

Lorsque votre application React est massive, il faut vraiment du temps pour qu'elle soit opérationnelle après le chargement de la page. Disons que vous montez votre partie React de l'application à #app . En général, cet élément dans votre index.html est simplement un div vide :

<div id="app"></div>

Ce que vous pouvez faire à la place est de mettre un peu de style et un tas d'images pour améliorer l'apparence entre le chargement de la page et le rendu initial de l'application React sur le DOM :

<div id="app">
  <div class="logo">
    <img src="/my/cool/examplelogo.svg" />
  </div>
  <div class="preload-title">
    Hold on, it's loading!
  </div>
</div>

Après le chargement de la page, l'utilisateur verra immédiatement le contenu original de index.html. Peu de temps après, lorsque React est prêt à monter toute la hiérarchie des composants rendus sur ce nœud DOM, l'utilisateur verra l'application actuelle.

Note class pas className . C'est parce que vous devez mettre ceci dans votre fichier html.


Si vous utilisez le SSR, les choses sont moins compliquées car l'utilisateur verra l'application réelle juste après le chargement de la page.

0 votes

Cela fonctionne également à deux endroits où le chargement se produit. L'un est le application massive. et le suivant est le préparation (montage de divers composants). J'obtiens donc une étape de clignotement parce que l'app.render prend le relais et l'animation est réinitialisée ( a remplacé vraiment). Y aurait-il un moyen d'éviter ce flash ? React comparera-t-il le DOM un par un ? Mais d'après ce que je comprends, React ajoute toutes sortes de données privées dans les balises...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X