Мета
Коли візуалізація 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 -->
Приклад 2
Варіантом використання :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 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, і передати їх в кореневий контейнер з допомогою реквізиту. Функції можуть управляти DOM або робити все необхідне для управління прядильником. Таким чином, React не знає про "зовнішній світ", і не потребує безпосереднього контролю над DOM. Ви можете легко замінити функції для тестування, або якщо вам потрібно змінити логіку, і ви можете передати їх іншим компонентам дерева реагування.
Приклад 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>