【问题标题】:React - Display loading screen while DOM is rendering?React - 在 DOM 渲染时显示加载屏幕?
【发布时间】:2017-04-20 14:48:04
【问题描述】:

这是来自 Google Adsense 应用程序页面的示例。在主页面之后显示的加载屏幕。

我不知道如何用 React 做同样的事情,因为如果我让 React 组件渲染加载屏幕,它不会在页面加载时显示,因为它必须等待 DOM 之前渲染。

更新

我通过将屏幕加载器放入 index.html 并在 React componentDidMount() 生命周期方法中将其删除来举例说明我的方法。

Examplereact-loading-screen

【问题讨论】:

  • 显示你想在纯 js 中显示的内容,然后在 react 挂载后将其隐藏或从 DOM 中删除。您需要做的就是将其从反应代码中隐藏起来。
  • 这简直太棒了!谢谢。
  • 同意这是一个很棒的方法。我已经发布了几个 react 应用程序,我将加载屏幕放在
    内(有效),但在第一次调用 ReactDOM 之间可能会有一个短暂的“白屏”。 render() 以及组件何时实际绘制。使用加载屏幕的固定定位,然后使用 CSS 的 componentDidUpdate(或 useEffect 钩子)淡化然后完全删除它是美妙的。它确保您不会移除加载屏幕,直到您的完全绘制的 React 组件已经在下面,可以查看了。

标签: reactjs asynchronous redux react-dom


【解决方案1】:

解决方法是:

在你的渲染函数中做这样的事情:

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

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

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

在构造函数中将 isLoading 初始化为 true,在 componentDidMount 上初始化为 false

【讨论】:

  • 当我们调用 ajax 方法将数据加载到子组件时。 componentDidMount 在子组件数据填充之前调用。我们如何克服这类问题?
  • 挂载生命周期没问题,要不要为更新生命周期添加一些东西?
  • 我必须在所有页面中执行此操作还是仅在应用程序条目中执行此操作
【解决方案2】:

这可以通过将加载图标放置在您的 html 文件(例如 index.html)中来完成,以便用户在 html 文件加载后立即看到该图标。

当您的应用完成加载后,您可以简单地在生命周期挂钩中删除该加载图标,我通常在 componentDidMount 中这样做。

【讨论】:

  • 如果将根组件挂载到该图标的父节点,甚至不需要手动删除它。 React 会清理挂载节点的子节点,并把它自己新渲染的 DOM 放在那里。
  • 我没有将图标放在 React 应用程序的根节点中,我觉得它不合适
  • PWA 有什么缺点吗?它会干扰默认启动画面吗?
  • @benmneb 有干扰吗?
【解决方案3】:

当您的 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,事情就不那么复杂了,因为用户会在页面加载后立即看到真正的应用程序。

【讨论】:

  • 这也有效,我有两个加载发生的地方。一个是massive app.,接下来是preparation(各种组件的安装)。所以我得到了一个闪烁的步骤,因为app.render接管并且动画被重置(真的替换了。)有没有办法避免这种闪光? React 会一对一地比较 DOM 吗?但据我了解,React 在标签中添加了各种私有数据......
【解决方案4】:

目标

当呈现 html 页面时,立即显示一个微调器(在 React 加载时),并在 React 准备好后隐藏它。

由于微调器是用纯 HTML/CSS(在 React 域之外)呈现的,React 不应该直接控制显示/隐藏过程,并且实现对 React 应该是透明的。

解决方案 1 - :empty 伪类

由于您将 react 渲染到 DOM 容器 - &lt;div id="app"&gt;&lt;/div&gt;,因此您可以向该容器添加一个微调器,当 react 加载并渲染时,微调器将消失。

您不能在 react 根中添加 DOM 元素(例如 div),因为一旦调用 ReactDOM.render(),React 就会替换容器的内容。即使您渲染null,内容仍会被评论替换 - &lt;!-- react-empty: 1 --&gt;。这意味着,如果您想在主组件挂载时显示加载器,正在加载数据,但实际上没有渲染任何内容,则放置在容器内的加载器标记(例如&lt;div id="app"&gt;&lt;div class="loader"&gt;&lt;/div&gt;&lt;/div&gt;)将不起作用。

解决方法是将微调器类添加到反应容器,并使用:empty pseudo class。只要容器中没有渲染任何内容(cmets 不计算在内),微调器就会可见。一旦 react 渲染了评论以外的东西,加载器就会消失。

示例 1

在示例中,您可以看到一个组件在准备好之前呈现null。容器也是加载器 - &lt;div id="app" class="app"&gt;&lt;/div&gt;,加载器的类只有在 :empty 时才能工作(参见代码中的 cmets):

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

示例 2

使用:empty 伪类来显示/隐藏选择器的一种变体是将微调器设置为应用容器的同级元素,并使用adjacent sibling combinator (@ 987654337@):

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>

解决方案 2 - 将微调器“处理程序”作为道具传递

要对微调器显示状态进行更细粒度的控制,请创建两个函数 showSpinnerhideSpinner,并通过 props 将它们传递给根容器。这些函数可以操作 DOM,或者做任何需要控制微调器的事情。这样,React 就不会感知“外界”,也不需要直接控制 DOM。您可以轻松替换功能进行测试,或者如果您需要更改逻辑,您可以将它们传递给 React 树中的其他组件。

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

示例 2 - 钩子

本示例使用useEffect 挂钩在组件挂载后隐藏微调器。

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>

【讨论】:

  • 您能澄清一下最后两个代码部分的位置吗?第一个显然在 react 组件的 javascript src 文件中,我猜第三个在要由所述 js 文件呈现的 html 模板中,但是第二个去哪里?
  • 第二个是CSS。我使用了全局 CSS,但您可以在 JS 中使用 CSS 模块或 CSS。第三个是 HTML 文件,如果需要,它可能包含微调器标记(第二个示例)。
  • @dryleaf - setTimeout 不是解决方案的一部分。它模拟在呈现内容之前等待异步操作。
  • @hamza-jutt - 你应该就此提出一个新问题。
  • return null 添加评论,将呈现为空白屏幕。 :empty 伪类与 return null 一起工作,因为它在确定容器是否为空时会忽略 cmets。
【解决方案5】:

如果有人为上述用例寻找插入式、零配置和零依赖库,请尝试使用 pace.js (https://codebyzach.github.io/pace/docs/)。

它自动挂钩事件(ajax、readyState、历史推送状态、js 事件循环等)并显示可定制的加载器。

与我们的 react/relay 项目配合得很好(使用 react-router 处理导航更改,中继请求) (不附属;我们的项目中使用了 pace.js,效果很好)

【讨论】:

  • 嘿!你能告诉我如何在 react 中使用它吗?
  • 只需将脚本附加到public/index.html 并选择一种样式。这是非常简单,令人惊叹的插件。谢谢。
  • 如果没有这个答案,我就不会找到节奏。它非常容易包含,并且通过一点 CSS 魔法和一些事件附件,我能够在转换期间阻止/禁用应用程序并自定义微调器。
【解决方案6】:

我最近不得不处理这个问题并想出了一个对我来说很好的解决方案。但是,我已经尝试了上面的@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 属性添加到微调器,因此实际上它只会在整个应用加载时添加一次。

【讨论】:

    【解决方案7】:

    我正在使用 react-progress-2 npm 包,它是零依赖的,在 ReactJS 中效果很好。

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

    安装:

    npm install react-progress-2

    将 react-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.
    }
    

    请注意,showhide 调用是堆叠的,因此在连续 n 次显示调用后,您需要执行 n 次隐藏调用以隐藏指示器,或者您可以使用 Progress.hideAll()

    【讨论】:

      【解决方案8】:

      在 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;
          }
        }
      

      【讨论】:

        【解决方案9】:

        我也在我的应用程序中使用 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 中使用伪元素实现您的加载器(或将类或 id 添加到不同的元素,而不是您喜欢的主体) - 您可以将背景颜色设置为不透明或透明等...示例:

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

        【讨论】:

          【解决方案10】:

          编辑 public 文件夹中的 index.html 文件位置。将您的图像复制到公共文件夹中与 index.html 相同的位置。 然后将index.html中包含&lt;div id="root"&gt; &lt;/div&gt;标签的部分内容替换为下面给出的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 替换。

          【讨论】:

            【解决方案11】:

            最重要的问题是:“加载”是什么意思?如果您谈论的是要安装的物理元素,那么这里的一些第一个答案很棒。但是,如果您的应用首先要做的是检查身份验证,那么您真正加载的是来自后端的数据,无论用户是否传递了一个将他们标记为授权用户或未授权用户的 cookie。

            这是基于 redux 的,但您可以轻松地将其更改为普通的反应状态模型。

            动作创建者:

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

            finally 部分表示用户是否经过身份验证,收到响应后加载屏幕消失。

            这是加载它的组件的样子:

            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 变为 falsy。我们的组件更新,再次调用 render,这次因为 if 语句没有加载屏幕。

            【讨论】:

              【解决方案12】:

              现在我们也可以在 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;
              

              【讨论】:

              • 你没有抓住重点,在 bundle.js 加载之前没有反应。 Html 在任何脚本之前加载,因此应该显示一个加载页面。
              【解决方案13】:

              这将在 ReactDOM.render() 控制 root &lt;div&gt; 之前发生。 IE。到那时,您的应用还没有安装。

              因此,您可以将加载程序添加到根&lt;div&gt; 内的index.html 文件中。在 React 接管之前,这将在屏幕上可见。

              您可以使用最适合您的任何加载器元素(例如svg 与动画)。

              您不需要在任何生命周期方法中删除它。 React 会将其 root &lt;div&gt; 的任何子代替换为您呈现的 &lt;App/&gt;,如下面的 GIF 所示。

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

              ReactDOM.render() 运行之前使用debugger 检查页面。

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

              【讨论】:

              • 美观优雅的解决方案
              【解决方案14】:

              react 应用的启动是基于主包下载的。 React 应用程序仅在浏览器中下载主包后启动。在延迟加载架构的情况下也是如此。 但事实是我们无法准确说明任何捆绑包的名称。因为当你运行“npm run build”命令时,webpack 会在每个包的末尾添加一个哈希值。当然我们可以通过更改哈希设置来避免这种情况,但它会严重影响浏览器中的缓存数据问题。由于捆绑名称相同,浏览器可能不会采用新版本。 . 我们需要一个 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>

              在您的生产 webpack 配置中,将 HtmlWebpackPlugin 选项更改为以下

               new HtmlWebpackPlugin({
                        inject: false,
              ...
              

              您可能需要使用“弹出”命令来获取配置文件。最新的 webpack 可能有选项来配置 HtmlWebpackPlugin 而不弹出项目。

              【讨论】:

                【解决方案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>
                

                您可以使用HTMLCSS 使其看起来像您的示例。

                【讨论】:

                  【解决方案16】:

                  我还使用了@Ori Drori 的答案并设法让它发挥作用。随着您的 React 代码的增长,客户端浏览器在首次访问时必须下载的编译包也会增长。如果处理不好,这会带来用户体验问题。

                  我在@Ori答案中添加的是在body标签的onload属性的index.html中添加并执行onload函数,以便加载器在浏览器中完全加载后消失,见sn-下图:

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

                  【讨论】:

                    【解决方案17】:

                    Pace 怎么样

                    在此处使用此链接地址。

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

                    1.在他们的网站上选择你想要的样式并粘贴到 index.css

                    2.转到 cdnjs 复制 Pace Js 的链接并添加到 public/index.html 中的脚本标签

                    3.它会自动检测网页加载并在浏览器顶部显示速度。

                    你也可以在css中修改高度和动画。

                    【讨论】:

                    • 很棒,可以立即集成。
                    【解决方案18】:

                    这是我的实现,基于答案

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

                    【讨论】:

                      【解决方案19】:

                      您可以通过在 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 渲染时,您的加载组件都将按照我们在上面的 fallback 属性中指定的方式显示。只需确保您只在 componentDidMount()

                      中执行任何 ajax 请求

                      【讨论】:

                      • 你有这种解决方案的 GitHub 示例吗?
                      【解决方案20】:

                      我不知道现在回答是否为时已晚,因为您可能已经找到了解决方案,但这是我为未来来者准备的一个问题,因为这个问题确实很有用。 :
                      我从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 并一直保持,直到接收到数据,然后在接收到数据时更改状态并将 loading 设置为 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;
                      

                      所以,与钩子相同的逻辑。在加载数据时,我得到了漂亮的微调器,然后是我的数据!

                      哦,顺便说一句,如果你不喜欢这个 XD,你可以把你自己的 API 放到 fetch 中。

                      【讨论】:

                        【解决方案21】:

                        这个问题可以通过 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
                        

                        当仪表板组件仍在加载时,将显示加载组件。

                        【讨论】:

                        • 这是一个很好的解决方案,不知道为什么它没有得到更多的关注! reactjs.org/docs/react-api.html#reactlazy
                        • 这仅在 React 作为捆绑包的一部分下载后才有效。因此,在这种情况发生之前,您仍然会看到白屏。
                        【解决方案22】:

                        如果您使用react-router 管理应用程序的路由,您可以使用我制作的react-router-loading 库轻松添加加载屏幕。

                        也会影响页面切换,不过我觉得如果要预加载第一页,自然也预加载其他页面。

                        此方法与Suspense 的区别在于,使用此库,您可以在获取数据等的同时继续加载。 基本上这种方法与在组件中使用isLoading 状态非常相似,但如果您有很多不同的页面,则更容易实现。

                        用法

                        在您的路由器部分从react-router-loading 导入SwitchRoute 而不是react-router-dom

                        import { Switch, Route } from "react-router-loading";
                        
                        <Switch>
                            <Route path="/page1" component={Page1} />
                            <Route path="/page2" component={Page2} />
                            ...
                        </Switch>
                        

                        为每条切换前必须加载的路由添加loading prop

                        <Switch>
                            // data will be loaded before switching
                            <Route path="/page1" component={Page1} loading />
                        
                            // instant switch as before
                            <Route path="/page2" component={Page2} />
                            ...
                        </Switch>
                        

                        在带有loading 属性的路由中提到的组件中,在初始加载方法的末尾添加loadingContext.done()(在本例中为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>
                        

                        【讨论】:

                          【解决方案23】:

                          只需在&lt;div id="root"&gt;&lt;/div&gt; 标签内添加内容,您就可以开始了!

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

                          一旦&lt;App /&gt;被加载,React会自动忽略&lt;div id="root"&gt;标签内的所有内容,用你的实际应用覆盖它!

                          【讨论】:

                          • 是的,它是正确的。已经 React 将改变 #root inside
                          猜你喜欢
                          • 2019-09-28
                          • 2021-09-08
                          • 2020-11-23
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2020-03-21
                          • 1970-01-01
                          相关资源
                          最近更新 更多