Реагировать - Показать экран загрузки во время рендеринга DOM?

Это пример со страницы приложения Google Adsense. Экран загрузки, отображаемый перед главной страницей, отображался после.

введите описание изображения здесь

Я не знаю, как сделать то же самое с React, потому что, если я делаю загрузочный экран, отображаемый компонентом React, он не отображается во время загрузки страницы, потому что он должен ждать, пока DOM визуализируется раньше.

Обновлено:

Я сделал пример моего подхода, поместив загрузчик экрана в index.html и удалите его в React componentDidMount() метод жизненного цикла.

Пример: https://nguyenbathanh.github.io/

Источник: https://github.com/nguyenbathanh/react-loading-screen

25 ответов

Решение

Это можно сделать, поместив значок загрузки в HTML-файл (index.html для примера), чтобы пользователи увидели значок сразу после загрузки HTML-файла.

Когда ваше приложение завершает загрузку, вы можете просто удалить этот значок загрузки в хуке жизненного цикла, я обычно делаю это в componentDidMount,

Поскольку вы рендеритесь, реагируйте на DOM-контейнер <div id="app"></div>, вы можете добавить счетчик в этот контейнер, и когда реакция будет загружаться и рендеринга, счетчик исчезнет.

Эта проблема:

React заменит содержимое контейнера, как только ReactDOM.render() называется. Даже если вы оказываете null, контент все равно будет заменен комментарием - <!-- react-empty: 1 -->, Это означает, что если вы хотите отображать загрузчик во время рендеринга реакции, внутри контейнера размещается разметка загрузчика (<div id="app"><div class="loader"></div></div> например) не будет работать.

Решение:

Мое решение состоит в том, чтобы добавить класс загрузчика непосредственно в контейнер, но с :empty псевдо класс. Спиннер будет виден до тех пор, пока в контейнер ничего не будет выведено (комментарии не учитываются). Как только реагирует рендеринг элемента, загрузчик исчезнет.


В примере вы можете увидеть компонент, который делает null пока не готово. Контейнер также является загрузчиком - <div id="app" class="app"></div> и класс загрузчика будет работать только если :empty (см. комментарии в коде):

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

  componentDidMount() {
    // the setTimeout just simulates an async action, after which the component will render the content
    setTimeout(() => this.setState({ loading: false }), 1500);
  }
  
  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>
    ); 
  }
}

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 -->

Разновидность использования :empty псевдокласс для /show hide селектор, устанавливает спиннер в качестве элемента-брата для контейнера приложения и показывает его до тех пор, пока контейнер пуст, используя соседний комбинатор (+):

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

  componentDidMount() {
    setTimeout(() => this.setState({ loading: false }), 2500); // simulates loading of data
  }
  
  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>
    ); 
  }
}

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>

Обходной путь для этого:

В вашей функции рендеринга сделайте что-то вроде этого:

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

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

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

Инициализируйте isLoading как true в конструкторе и false в componentDidMount

Это произойдет раньше ReactDOM.render() берет под контроль корень <div>, Т.е. ваше приложение не будет установлено до этого момента.

Так что вы можете добавить свой загрузчик в свой index.html файл внутри корня <div>, И это будет видно на экране, пока React не вступит во владение.

Вы можете использовать любой элемент загрузчика, который вам больше подходит (svg с анимацией например).

Вам не нужно удалять его при любом методе жизненного цикла. React заменит любого потомка своего корня <div> с вашим оказанным <App/>, как мы можем видеть в GIF ниже.

Пример на CodeSandbox

index.html

<head>
  <style>
    .svgLoader {
      animation: spin 0.5s linear infinite;
      margin: auto;
    }
    .divLoader {
      width: 100vw;
      height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    @keyframes spin {
      0% { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }
  </style>
</head>

<body>
  <div id="root">
    <div class="divLoader">
      <svg class="svgLoader" viewBox="0 0 1024 1024" width="10em" height="10em">
        <path fill="lightblue"
          d="PATH FOR THE LOADER ICON"
        />
      </svg>
    </div>
  </div>
</body>

index.js

С помощью debugger проверить страницу раньше ReactDOM.render() пробеги.

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

debugger; // TO INSPECT THE PAGE BEFORE 1ST RENDER

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Если кто-то ищет библиотеку drop-in, нулевой конфигурации и нулевой зависимости для вышеупомянутого варианта использования, попробуйте pace.js ( http://github.hubspot.com/pace/docs/welcome/).

Он автоматически подключается к событиям (ajax, readyState, pushstate истории, циклу событий js и т. Д.) И показывает настраиваемый загрузчик.

Хорошо работал с нашими проектами " Реакция / ретрансляция" (обрабатывает изменения навигации с помощью реагирующего маршрутизатора, запросов ретрансляции) (Не связан; использовал для наших проектов pace.js, и он работал отлично)

Просто добавьте контент внутри <div id="root"></div> тег, и вам должно быть хорошо!

      // Example:

<div id="root">
   <div id="pre-loader">
        <p>Loading Website...</p>
        <img src="/images/my-loader.gif" />
   </div>
</div>

Однажды <App /> загружен, React автоматически проигнорирует весь контент внутри <div id="root"> тег, перезаписав его вашим фактическим приложением!

Когда ваше приложение React является массовым, оно действительно требует времени для его запуска и запуска после загрузки страницы. Скажем, вы монтируете свою часть приложения React в #app, Обычно этот элемент в вашем index.html является просто пустым div:

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

Вместо этого вы можете поместить немного стиля и кучу изображений, чтобы он лучше выглядел между загрузкой страницы и начальным рендерингом приложения React в 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>

После загрузки страницы пользователь сразу увидит исходный контент index.html. Вскоре после этого, когда React будет готов смонтировать всю иерархию отображаемых компонентов в этом узле DOM, пользователь увидит реальное приложение.

Заметка classне className, Это потому, что вам нужно поместить это в ваш HTML-файл.


Если вы используете SSR, все будет не так сложно, потому что пользователь увидит настоящее приложение сразу после загрузки страницы.

В настоящее время мы можем использовать хуки и в React 16.8:

import React, { useState, useEffect } from 'react';

const app = () => {
  const [ spinner, setSpinner ] = useState(true);

  // It will be executed before rendering
  useEffect(() => {
    setTimeout(() => setSpinner(false), 1000)
  }, []);
  // [] means like componentDidMount

  return !spinner && <div >Your content</div>;
}

export default app;

Если вы используете react-routerдля управления маршрутами вашего приложения вы можете легко добавить экран библиотекой загрузки сзагрузки response-router-loading, которую я сделал.

Это также влияет на переключение страниц, но я думаю, что если вы хотите предварительно загрузить первую страницу, естественно предварительно загрузить и другие страницы.

Разница между этим методом и Suspenseзаключается в том, что с помощью этой библиотеки вы можете продолжать загрузку, пока вы извлекаете данные и так далее. В основном этот метод очень похож на использование isLoading состояние внутри компонента, но гораздо проще реализовать, если у вас много разных страниц.

использование

В разделе вашего роутера import Switch а также Route из react-router-loading вместо того react-router-dom

      import { Switch, Route } from "react-router-loading";

<Switch>
    <Route path="/page1" component={Page1} />
    <Route path="/page2" component={Page2} />
    ...
</Switch>

Добавьте опору к каждому маршруту, который должен быть загружен перед переключением

      <Switch>
    // data will be loaded before switching
    <Route path="/page1" component={Page1} loading />

    // instant switch as before
    <Route path="/page2" component={Page2} />
    ...
</Switch>

Добавлять loadingContext.done() в конце вашего первоначального метода загрузки в компонентах, упомянутых в маршрутах с loading опора (в данном случае это Page1)

      import { LoadingContext } from "react-router-loading";
const loadingContext = useContext(LoadingContext);

const loading = async () => {
    // loading some data

    // call method to indicate that loading is done and we are ready to switch
    loadingContext.done();
};

Вы можете указать экран загрузки, который будет отображаться при первой загрузке вашего приложения.

      const MyLoadingScreen = () => <div>Loading...</div>

<Switch loadingScreen={MyLoadingScreen}>
...
</Switch>

Я не знаю, слишком ли поздно отвечать, поскольку вы, возможно, нашли решение, но вот один с моей стороны для будущих посетителей, так как этот вопрос действительно полезен. :
Я лекцию на прослушалscrimba.com, и здесь учитель начал с занятий, а затем занялся крючками. Он обучал вызову API через классы, состояние и все такое. Вот его код:

      import React, {Component} from "react"

class App extends Component {
    constructor() {
        super()
        this.state = {
            loading: false,
            character: {}
        }
    }
    
    componentDidMount() {
        this.setState({loading: true})
        fetch("https://swapi.dev/api/people/1/")
            .then(response => response.json())
            .then(data => {
                this.setState({
                    loading: false,
                    character: data
                })
            })
    }
    
    render() {
        const text = this.state.loading ? "loading..." : this.state.character.name
        return (
            <div>
                <p>{text}</p>
            </div>
        )
    }
}

export default App

Итак, это довольно просто: установите для состояния загрузки значение true в начале и сохраните его до тех пор, пока данные не будут получены, а затем, когда они будут получены, изменяет состояние и устанавливает для загрузки значение false и отображает содержимое.
Сейчас я попробовал это с крючками, как практика, и все получилось довольно гладко! Простое, но эффективное решение. Вот мой код:

      import React, {useState,useEffect} from 'react'

function App()
{
    const [response, setResponse] = useState([]);
    
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        fetchResponse() ;
    } , []);

    const fetchResponse = async () => {
        const data = await fetch("https://swapi.dev/api/people/1/");
        const response = await data.json();

        setResponse(response);
        console.log(response.name);
        setLoading(false);
    } 

        const content = loading ? <i className="fas fa-atom fa-spin"></i> : <h1>{response.name}</h1>

    return(
        <section id="w-d-p">
            {content}
        </section>
    )
}

export default App;

Итак, та же логика и с крючками. И я получаю красивый спиннер, пока данные загружаются, а затем мои данные!

Да, и, кстати, вы можете добавить свой собственный API в выборку, если вам не нравится этот XD.

Недавно мне пришлось столкнуться с этой проблемой, и я нашел решение, которое прекрасно работает для меня. Тем не менее, я попробовал решение @Ori Drori выше и, к сожалению, оно не сработало правильно (были некоторые задержки + мне не нравится использование setTimeout функционировать там).

Вот что я придумал:

index.html файл

внутри head tag - стили для индикатора:

<style media="screen" type="text/css">

.loading {
  -webkit-animation: sk-scaleout 1.0s infinite ease-in-out;
  animation: sk-scaleout 1.0s infinite ease-in-out;
  background-color: black;
  border-radius: 100%;
  height: 6em;
  width: 6em;
}

.container {
  align-items: center;
  background-color: white;
  display: flex;
  height: 100vh;
  justify-content: center;
  width: 100vw;
}

@keyframes sk-scaleout {
  0% {
    -webkit-transform: scale(0);
    transform: scale(0);
  }
  100% {
    -webkit-transform: scale(1.0);
    opacity: 0;
    transform: scale(1.0);
  }
}

</style>

Теперь body тег:

<div id="spinner" class="container">
  <div class="loading"></div>
</div>

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

И тогда приходит очень простая логика, внутри app.js файл (в функции рендеринга):

const spinner = document.getElementById('spinner');

if (spinner && !spinner.hasAttribute('hidden')) {
  spinner.setAttribute('hidden', 'true');
}

Как это работает?

Когда первый компонент (в моем приложении это app.js в большинстве случаев) spinner скрывается с применением hidden приписать ему.

Что важнее добавить - !spinner.hasAttribute('hidden') условие мешает добавить hidden Атрибут для счетчика при каждом подключении компонента, поэтому на самом деле он будет добавлен только один раз, когда загружается все приложение.

Установка таймаута в componentDidMount работает, но в моем приложении я получил предупреждение об утечке памяти. Попробуйте что-то вроде этого.

constructor(props) {
    super(props)
    this.state = { 
      loading: true,
    }
  }
  componentDidMount() {
    this.timerHandle = setTimeout(() => this.setState({ loading: false }), 3500); 
  }

  componentWillUnmount(){
    if (this.timerHandle) {
      clearTimeout(this.timerHandle);
      this.timerHandle = 0;
    }
  }

Я использую пакет response -progress-2 npm, который имеет нулевую зависимость и прекрасно работает в ReactJS.

https://github.com/milworm/react-progress-2

Монтаж:

npm install react-progress-2

Включите response-progress-2/main.css в ваш проект.

import "node_modules/react-progress-2/main.css";

Включают react-progress-2 и поместите его где-нибудь в верхний компонент, например:

import React from "react";
import Progress from "react-progress-2";

var Layout = React.createClass({
render: function() {
    return (
        <div className="layout">
            <Progress.Component/>
                {/* other components go here*/}
            </div>
        );
    }
});

Теперь, когда вам нужно показать индикатор, просто позвоните Progress.show(), например:

loadFeed: function() {
    Progress.show();
    // do your ajax thing.
},

onLoadFeedCallback: function() {
    Progress.hide();
    // render feed.
}

Обратите внимание, что show а также hide вызовы составляются, поэтому после n последовательных вызовов необходимо выполнить n скрытых вызовов, чтобы скрыть индикатор, или вы можете использовать Progress.hideAll(),

Вам не нужно столько усилий, вот основной пример.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="logo192.png" />
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
  <title>Title</title>
  <style>
    body {
      margin: 0;
    }

    .loader-container {
      width: 100vw;
      height: 100vh;
      display: flex;
      overflow: hidden;
    }

    .loader {
      margin: auto;
      border: 5px dotted #dadada;
      border-top: 5px solid #3498db;
      border-radius: 50%;
      width: 100px;
      height: 100px;
      -webkit-animation: spin 2s linear infinite;
      animation: spin 2s linear infinite;
    }

    @-webkit-keyframes spin {
      0% {
        -webkit-transform: rotate(0deg);
      }

      100% {
        -webkit-transform: rotate(360deg);
      }
    }

    @keyframes spin {
      0% {
        transform: rotate(0deg);
      }

      100% {
        transform: rotate(360deg);
      }
    }

  </style>
</head>

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root">
    <div class="loader-container">
      <div class="loader"></div>
    </div>
  </div>
</body>

</html>

Вы можете поиграть с HTML а также CSS чтобы это выглядело как твой пример.

Вы можете легко сделать это, используя ленивую загрузку в react. Для этого вам нужно использовать ленивость и неопределенность от такой реакции.

import React, { lazy, Suspense } from 'react';

const loadable = (importFunc, { fallback = null } = { fallback: null }) => {
  const LazyComponent = lazy(importFunc);

  return props => (
    <Suspense fallback={fallback}>
      <LazyComponent {...props} />
    </Suspense>
  );
};

export default loadable;

После этого экспортируйте свои компоненты вот так.

export const TeacherTable = loadable(() =>
  import ('./MainTables/TeacherTable'), {
    fallback: <Loading />,
  });

А затем в вашем файле маршрутов используйте это так.

 <Route exact path="/app/view/teachers" component={TeacherTable} />

Вот и все, теперь вы можете идти каждый раз, когда ваша DOM визуализирует, ваш компонент Loading будет отображаться, как мы указали в резервном свойстве выше. Просто убедитесь, что вы выполняете любой запрос ajax только в componentDidMount()

Эту проблему легко решить с помощью ленивой функции React.

      import { Suspense, lazy } from "react"
import Loading from "components/Loading"

const Dashboard = lazy(() => import("containers/Dashboard"))

const App = () => (
  <Suspense fallback={<Loading />}>
    <Dashboard />
  </Suspense>
)

export default App

Компонент загрузки будет отображаться, пока загружается компонент Dashboard.

Это моя реализация, основанная на ответах

./public/index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <title>React App</title>
  <style>
    .preloader {
      display: flex;
      justify-content: center;
    }

    .rotate {
      animation: rotation 1s infinite linear;
    }

    .loader-hide {
      display: none;
    }

    @keyframes rotation {
      from {
        transform: rotate(0deg);
      }

      to {
        transform: rotate(359deg);
      }
    }
  </style>
</head>

<body>
  <div class="preloader">
    <img src="https://i.imgur.com/kDDFvUp.png" class="rotate" width="100" height="100" />
  </div>
  <div id="root"></div>
</body>

</html>

./src/app.js

import React, { useEffect } from "react";

import "./App.css";

const loader = document.querySelector(".preloader");

const showLoader = () => loader.classList.remove("preloader");
const addClass = () => loader.classList.add("loader-hide");

const App = () => {
  useEffect(() => {
    showLoader();
    addClass();
  }, []);
  return (
    <div style={{ display: "flex", justifyContent: "center" }}>
      <h2>App react</h2>
    </div>
  );
};

export default App;

Я также использую React в своем приложении. Для запросов я использую перехватчики axios, поэтому отличный способ сделать экран загрузчика (на полной странице, как вы показали пример) - это добавить класс или идентификатор, например, в тело внутри перехватчиков (здесь код из официальной документации с некоторым пользовательским кодом):

// Add a request interceptor
axios.interceptors.request.use(function (config) {
    // Do something before request is sent
     document.body.classList.add('custom-loader');
     return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
  });

// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Do something with response data
       document.body.classList.remove('custom-loader');
       return response;
  }, function (error) {
    // Do something with response error
    return Promise.reject(error);
  }); 

А затем просто внедрите в CSS ваш загрузчик с помощью псевдоэлементов (или добавьте класс или идентификатор к другому элементу, а не к телу, как вам нравится) - вы можете установить цвет фона на непрозрачный или прозрачный, и т. Д... Пример:

custom-loader:before {
    background: #000000;
    content: "";
    position: fixed;
    ...
}

custom-loader:after {
    background: #000000;
    content: "Loading content...";
    position: fixed;
    color: white;
    ...
}

Отредактируйте расположение файла index.html в общей папке. Скопируйте изображение в то же место, что и index.html в общей папке. А затем заменить часть содержимого index.html, содержащую <div id="root"> </div> теги к приведенному ниже HTML-коду.

<div id="root">  <img src="logo-dark300w.png" alt="Spideren" style="vertical-align: middle; position: absolute;
   top: 50%;
   left: 50%;
   margin-top: -100px; /* Half the height */
   margin-left: -250px; /* Half the width */" />  </div>

Логотип теперь будет отображаться в середине страницы в процессе загрузки. И будет заменен через несколько секунд на React.

А как насчет использования Pace

Используйте этот адрес ссылки здесь.

https://github.hubspot.com/pace/docs/welcome/

1. На их веб-сайте выберите нужный стиль и вставьте в index.css.

2. перейдите в cdnjs Скопируйте ссылку для Pace Js и добавьте в свои теги скрипта в public / index.html

3. Он автоматически определяет веб-нагрузки и отображает темп в верхней части браузера.

Вы также можете изменить высоту и анимацию в CSS.

Запуск приложения реакции основан на загрузке основного пакета. Приложение React запускается только после загрузки основного пакета в браузер. Это даже верно в случае ленивой загрузки архитектуры. Но дело в том, что мы не можем точно назвать название каких-либо пакетов. Потому что webapck будет добавлять хеш-значение в конце каждого пакета в то время, когда вы запускаете команду "npm run build". Конечно, этого можно избежать, изменив настройки хеша, но это серьезно повлияет на проблему с данными кеша в браузере. Браузеры могут не брать новую версию из-за того же имени пакета., нам нужен подход webpack + js + CSS для решения этой ситуации.

измените public/index.html, как показано ниже

<!DOCTYPE html>
<html lang="en" xml:lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=3.0, shrink-to-fit=no">
  <meta name="theme-color" content="#000000">
  <!--
      manifest.json provides metadata used when your web app is added to the
      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
    -->
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
  <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
  <style>
 .percentage {
      position: absolute;
      top: 50%;
      left: 50%;
      width: 150px;
      height: 150px;
      border: 1px solid #ccc;
      background-color: #f3f3f3;
      -webkit-transform: translate(-50%, -50%);
          -ms-transform: translate(-50%, -50%);
              transform: translate(-50%, -50%);
      border: 1.1em solid rgba(0, 0, 0, 0.2);
      border-radius: 50%;
      overflow: hidden;
      display: -webkit-box;
      display: -ms-flexbox;
      display: flex;
      -webkit-box-pack: center;
          -ms-flex-pack: center;
              justify-content: center;
      -webkit-box-align: center;
          -ms-flex-align: center;
              align-items: center;
    }

    .innerpercentage {
      font-size: 20px;
    }
  </style>
  <script>
    function showPercentage(value) {
      document.getElementById('percentage').innerHTML = (value * 100).toFixed() + "%";
    }
    var req = new XMLHttpRequest();
    req.addEventListener("progress", function (event) {
      if (event.lengthComputable) {
        var percentComplete = event.loaded / event.total;
        showPercentage(percentComplete)
        // ...
      } else {
        document.getElementById('percentage').innerHTML = "Loading..";
      }
    }, false);

    // load responseText into a new script element
    req.addEventListener("load", function (event) {
      var e = event.target;
      var s = document.createElement("script");
      s.innerHTML = e.responseText;
      document.documentElement.appendChild(s);
      document.getElementById('parentDiv').style.display = 'none';

    }, false);

    var bundleName = "<%= htmlWebpackPlugin.files.chunks.main.entry %>";
    req.open("GET", bundleName);
    req.send();

  </script>
  <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->

  <title>App Name</title>
  <link href="<%= htmlWebpackPlugin.files.chunks.main.css[0] %>" rel="stylesheet">
</head>

<body>
  <noscript>
    You need to enable JavaScript to run this app.
  </noscript>
  <div id="parentDiv" class="percentage">
    <div id="percentage" class="innerpercentage">loading</div>
  </div>
  <div id="root"></div>
  <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
</body>

</html>

В конфигурации вашего производственного веб-пакета измените параметр HtmlWebpackPlugin ниже

 new HtmlWebpackPlugin({
          inject: false,
...

Возможно, вам придется использовать команду "eject" для получения файла конфигурации. В последнем веб-пакете может быть возможность настроить HtmlWebpackPlugin без удаления проекта.

Я также использовал ответ @Ori Drori, и мне удалось заставить его работать. По мере роста вашего кода React будут скомпилированы пакеты, которые клиентский браузер должен будет загрузить при первом доступе. Это создает проблемы с пользовательским интерфейсом, если вы не справляетесь с этим должным образом.

В ответ @Ori я добавил и выполнил функцию onload в атрибуте index.html on onload тега body, чтобы загрузчик исчезал после того, как все было полностью загружено в браузере, см. Фрагмент ниже:

<html>
  <head>
     <style>
       .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);
          }
        }
     </style>
     <script>
       function onLoad() {
         var loader = document.getElementById("cpay_loader");loader.className = "";}
     </script>
   </head>
   <body onload="onLoad();">
     more html here.....
   </body>
</html>

Самый важный вопрос: что вы подразумеваете под "загрузкой"? Если вы говорите о монтируемом физическом элементе, некоторые из первых ответов здесь великолепны. Однако, если первое, что делает ваше приложение, это проверка на аутентификацию, то, что вы действительно загружаете, это данные из бэкэнда, передал ли пользователь cookie, который помечает их как авторизованного или неавторизованного пользователя.

Это основано на избыточности, но вы можете легко изменить ее на модель простого состояния реакции.

создатель действия:

export const getTodos = () => {
  return async dispatch => {
    let res;
    try {
      res = await axios.get('/todos/get');

      dispatch({
        type: AUTH,
        auth: true
      });
      dispatch({
        type: GET_TODOS,
        todos: res.data.todos
      });
    } catch (e) {
    } finally {
      dispatch({
        type: LOADING,
        loading: false
      });
    }
  };
};

Последняя часть означает, авторизован ли пользователь или нет, экран загрузки исчезает после получения ответа.

Вот как может выглядеть загружаемый компонент:

class App extends Component {
  renderLayout() {
    const {
      loading,
      auth,
      username,
      error,
      handleSidebarClick,
      handleCloseModal
    } = this.props;
    if (loading) {
      return <Loading />;
    }
    return (
      ...
    );
  }

  ...

  componentDidMount() {
    this.props.getTodos();
  }

...

  render() {
    return this.renderLayout();
 }

}

Если state.loading правдив, мы всегда увидим экран загрузки. В componentDidMount мы вызываем нашу функцию getTodos, которая является создателем действия, который превращает state.loading в ложь, когда мы получаем ответ (что может быть ошибкой). Наш компонент обновляется, вызовы снова отображаются, и на этот раз экран загрузки отсутствует из-за оператора if.

Из документации React, .

Функция React.lazy позволяет отображать динамический импорт как обычный компонент.

Это автоматически загрузит пакет, содержащий OtherComponent, при первом отображении этого компонента.

React.lazy берет функцию, которая должна вызывать динамический import(). Это должно вернуть обещание, которое разрешается в модуль с экспортом по умолчанию, содержащим компонент React.

Затем ленивый компонент должен быть отрендерен внутри компонента Suspense, что позволяет нам отображать некоторый резервный контент (например, индикатор загрузки), пока мы ждем загрузки ленивого компонента.

      import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

Резервная опора принимает любые элементы React, которые вы хотите отобразить, ожидая загрузки компонента. Вы можете разместить компонент Suspense в любом месте над ленивым компонентом. Вы даже можете обернуть несколько ленивых компонентов одним компонентом Suspense.

источникИсточник

Вот как я это делаю,

index.html

      <!DOCTYPE html>
<html lang="en">
  <head>
    <title>React App</title>
    <style>
      #loader-wrapper {
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        position: fixed;
        background-color: white;
        z-index: 1000;
      }

      #loader-wrapper>img {
        width: 10vw;
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        margin: auto;
      }
    </style>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <div id="loader-wrapper">
      <img src="./svg/loader.svg" />
    </div>
  </body>
</html>

В App.js импортируйте Jquery и закройте div из index.html.

      import { $ }  from 'react-jquery-plugin';
...
...
useEffect(() => {
  $("#loader-wrapper").fadeOut();
}, []);
Другие вопросы по тегам