Подтвердить что ты не робот

React - отображает экран загрузки во время рендеринга DOM?

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

enter image description here

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

Обновленный:

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

Пример и реагирующий экран загрузки.

4b9b3361

Ответ 1

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

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

Ответ 2

Цель

Когда рендеринг html-страницы выполняется, немедленно отобразите счетчик (пока загружается React) и скройте его после того, как React будет готов.

Поскольку спиннер отображается в чистом HTML/CSS (за пределами домена React), React не должен напрямую управлять процессом показа/скрытия, а реализация должна быть прозрачной для React.

Решение 1 - пустой псевдокласс

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

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

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

Пример 1

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

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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

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

Ответ 5

Когда ваше приложение 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 loading!
  </div>
</div>

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

Примечание class, а не className. Это потому, что вам нужно поместить это в свой html файл.


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

Ответ 6

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

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

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

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

Пример на CodeSandbox

enter image description here

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

Ответ 7

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

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

index.html файл

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

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

Ответ 8

Я использую пакет 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().

Ответ 9

Установка таймаута в 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;
    }
  }

Ответ 10

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

// 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;
    ...
}

Ответ 11

В настоящее время мы можем использовать хуки и в 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;

Ответ 12

Отредактируйте расположение файла 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.

Ответ 13

Самый важный вопрос: что вы подразумеваете под "загрузкой"? Если вы говорите о монтируемом физическом элементе, некоторые из первых ответов здесь великолепны. Однако, если первое, что делает ваше приложение, это проверка на аутентификацию, то, что вы действительно загружаете, это данные из бэкэнда, передал ли пользователь 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.

Ответ 14

Запуск приложения реакции основан на загрузке основного пакета. Приложение 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>

Ответ 15

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

<!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, чтобы они выглядели как ваш пример.