225

これは、Google Adsense アプリケーション ページの例です。メインページが表示される前に表示された読み込み画面。

ここに画像の説明を入力

Reactコンポーネントでレンダリングされたロード画面を作成すると、DOMがレンダリングされるのを待つ必要があるため、ページのロード中に表示されないため、Reactで同じことを行う方法がわかりません。

更新日:

index.htmlスクリーンローダーを入れて、ReactcomponentDidMount()ライフサイクルメソッドで削除することで、私のアプローチの例を作りました。

react-loading-screen

4

23 に答える 23

237

目標

HTML ページがレンダリングされると、すぐに (React の読み込み中) スピナーを表示し、React の準備ができたら非表示にします。

スピナーは純粋な HTML/CSS (React ドメインの外部) でレンダリングされるため、React は表示/非表示プロセスを直接制御するべきではなく、実装は React に対して透過的でなければなりません。

解決策 1 - :empty 疑似クラス

反応を DOM コンテナーにレンダリングするため<div id="app"></div>、そのコンテナーにスピナーを追加できます。反応がロードされてレンダリングされると、スピナーは消えます。

ReactDOM.render()Reactは呼び出されるとすぐにコンテナーの内容を置き換えるため、react ルート内に DOM 要素 (たとえば div) を追加することはできません。をレンダリングしても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 - スピナーの「ハンドラー」を小道具として渡す

スピナーの表示状態をよりきめ細かく制御するには、 と の 2 つの関数showSpinnerを作成hideSpinnerし、小道具を介してルート コンテナーに渡します。関数は 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>

于 2016-12-06T06:29:32.083 に答える
136

これは、html ファイル (ex では index.html) に読み込みアイコンを配置することで実行できます。これにより、html ファイルが読み込まれた直後にユーザーにアイコンが表示されます。

アプリの読み込みが完了したら、ライフサイクル フックでその読み込みアイコンを削除するだけで済みます。通常はcomponentDidMount.

于 2016-12-06T05:51:59.050 に答える
56

これに対する回避策は次のとおりです。

レンダー関数で次のようにします。

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

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

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

コンストラクターで isLoading を true として初期化し、componentDidMount で false として初期化します。

于 2016-12-06T05:46:23.177 に答える
22

上記のユースケース用のドロップイン、ゼロ構成、ゼロ依存ライブラリを探している人は、pace.js ( https://codebyzach.github.io/pace/docs/ ) を試してください。

イベント (ajax、readyState、history pushstate、js イベント ループなど) に自動的にフックし、カスタマイズ可能なローダーを表示します。

反応/リレー プロジェクトでうまく機能しました (反応ルーター、リレー リクエストを使用してナビゲーションの変更を処理します) (提携していません; 私たちのプロジェクトでは、pace.js を使用していましたが、うまくいきました)

于 2017-04-22T02:33:31.567 に答える
14

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 を使用すると、ページが読み込まれた直後にユーザーが実際のアプリを実際に見ることができるため、物事はそれほど複雑ではありません。

于 2016-12-06T05:55:22.937 に答える
5

私は最近その問題に対処しなければならず、私にとってはうまくいく解決策を思いついた. ただし、上記の @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ファイル内 (render 関数内) の非常に単純なロジックが続きます。

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

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

それはどのように機能しますか?

最初のコンポーネント (私のアプリでapp.jsはほとんどの場合) が正しくマウントされると、属性がspinner適用されて非表示になります。hidden

追加することがより重要なこと - !spinner.hasAttribute('hidden')条件は、すべてのコンポーネントのマウントでスピナーに属性を追加することを防ぎhiddenます。そのため、実際には、アプリ全体がロードされるときに 1 回だけ追加されます。

于 2017-10-18T10:28:13.887 に答える
1

最も重要な質問は、「ロード」とは何を意味するかということです。マウントされている物理要素について話している場合、ここでの最初の回答のいくつかは素晴らしいものです。ただし、アプリが最初に認証のチェックを行う場合、実際にロードするのはバックエンドからのデータであり、ユーザーが承認済みユーザーまたは未承認ユーザーのラベルを付ける 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
      });
    }
  };
};

最後の部分は、ユーザーが認証されているかどうかを意味し、応答を受信するとロード画面が消えます。

それをロードするコンポーネントは次のようになります。

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 が true の場合、常に読み込み画面が表示されます。componentDidMount で、getTodos 関数を呼び出します。これは、応答を受け取ったときに state.loading を false にするアクション クリエーターです (これはエラーになる可能性があります)。コンポーネントが更新され、render が再度呼び出されますが、今回は if ステートメントのために読み込み画面はありません。

于 2019-04-04T16:32:24.187 に答える