这是一个来自谷歌Adsense应用页面的例子。加载界面显示在主界面之前。
我不知道如何用React做同样的事情,因为如果我用React组件渲染加载屏幕,它不会在页面加载时显示,因为它必须等待DOM渲染之前。
更新:
我通过将屏幕加载器放在index.html中并在React componentDidMount()生命周期方法中删除它来举例说明我的方法。
示例和反应加载屏幕。
这是一个来自谷歌Adsense应用页面的例子。加载界面显示在主界面之前。
我不知道如何用React做同样的事情,因为如果我用React组件渲染加载屏幕,它不会在页面加载时显示,因为它必须等待DOM渲染之前。
更新:
我通过将屏幕加载器放在index.html中并在React componentDidMount()生命周期方法中删除它来举例说明我的方法。
示例和反应加载屏幕。
当前回答
我们的目标
当html页面被渲染时,立即显示一个转轮(在React加载时),并在React准备好后隐藏它。
由于旋转器是在纯HTML/CSS中呈现的(在React域之外),React不应该直接控制显示/隐藏过程,实现对React应该是透明的。
解决方案1 -:empty伪类
因为你把react渲染到一个DOM容器中- <div id="app"></div>,你可以在这个容器中添加一个旋转器,当react加载并渲染时,旋转器将消失。
You can't add a DOM element (a div for example) inside the react root, since React will replace the contents of the container as soon as ReactDOM.render() is called. Even if you render null, the content would still be replaced by a comment - <!-- react-empty: 1 -->. This means that if you want to display the loader while the main component mounts, data is loading, but nothing is actually rendered, a loader markup placed inside the container (<div id="app"><div class="loader"></div></div> for example) would not work.
一个解决方法是将spinner类添加到react容器中,并使用:empty伪类。旋转器将是可见的,只要没有任何东西被呈现到容器中(注释不算数)。一旦react渲染了注释以外的东西,加载器就会消失。
示例1
在这个例子中,你可以看到一个组件在准备好之前呈现为空。容器也是加载器- <div id="app" class="app"></div>,并且加载器的类只有在它为:空时才会工作(见代码中的注释):
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伪类来显示/隐藏选择器的一种变体是将spinner设置为应用容器的兄弟元素,并且只要容器为空,就使用相邻的兄弟组合子(+)显示它:
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 -传递旋转器“处理程序”作为道具
要对旋转器显示状态进行更细粒度的控制,可以创建两个函数showSpinner和hideSpinner,并通过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-router来管理你的应用程序的路由,你可以很容易地添加加载屏幕与我做的react-router加载库。
它也会影响页面切换,但我认为,如果你想预装第一页,自然也会预装其他页面。
这个方法和悬疑方法的区别在于,有了这个库,你可以在获取数据的同时继续加载。 基本上,这个方法非常类似于在组件中使用isLoading状态,但是如果您有很多不同的页面,则更容易实现。
使用
在路由器部分,从react-router-loading导入Switch和Route,而不是从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()(在这种情况下是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>
我最近不得不处理这个问题,并想出了一个解决方案,这对我来说很好。然而,我已经尝试了上面的@Ori Drori解决方案,不幸的是,它并没有正确工作(有一些延迟+我不喜欢setTimeout函数的使用)。
这是我想到的:
index . html文件
内头标签-指示器的样式:
<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. hasattribute ('hidden')条件阻止在每次组件挂载时向spinner添加隐藏属性,因此实际上它只会在整个应用程序加载时添加一次。
我还使用了@Ori Drori的答案,并设法让它起作用。随着React代码的增长,客户端浏览器在第一次访问时必须下载的编译包也会增加。如果你处理不好,这会带来用户体验问题。
我添加到@Ori回答是添加和执行onload函数在index.html的onload属性的主体标签,以便加载器消失后,一切都已完全加载在浏览,见下面的代码片段:
<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>
来自React文档,源代码。
React.lazy function lets you render a dynamic import as a regular component. This will automatically load the bundle containing the OtherComponent when this component is first rendered. React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component. The lazy component should then be rendered inside a Suspense component, which allows us to show some fallback content (such as a loading indicator) while we’re waiting for the lazy component to load.
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
回退道具接受你想渲染的任何React元素 在等待组件加载时。你可以设置悬念 组件位于惰性组件之上。你甚至可以包裹 多个惰性组件和一个悬疑组件。
源
在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;
}
}