序章

無限スクロールとは、ユーザーがページの下部に到達し、新しいコンテンツがフェッチおよびロードされるときに、ユーザーが比較的シームレスなエクスペリエンスでスクロールを継続できるようにすることです。 これは、より多くのコンテンツをロードする番号付きのページまたはボタンを使用する他のページネーションソリューションの代替手段です。

Instagramのようなアプリケーションで無限のスクロールに遭遇したかもしれません。 画像のフィードが表示され、下にスクロールすると、さらに多くの画像が表示され続けます。 彼らがあなたに与えるコンテンツを使い果たすまで、何度も何度も何度も。

このチュートリアルでは、無限スクロールを機能させる2つの重要な概念に触れます。ユーザーがページの下部に到達したことを検出し、表示するコンテンツの次のバッチをロードします。 これらの概念を使用して、天文学の写真やビデオの表示を作成します。

前提条件

このチュートリアルを完了するには、次のものが必要です。

このチュートリアルは、ノードv14.12.0、npm v6.14.8、react v16.13.1、superagent v6.1.0、およびlodash.debouncev2.7.1で検証されました。 。

ステップ1—プロジェクトの設定

create-react-app を使用してReactアプリを生成し、依存関係をインストールすることから始めます。

  1. npx create-react-app react-infinite-scroll-example

新しいプロジェクトディレクトリに移動します。

  1. cd react-infinite-scroll-example

APOD APIからデータをロードするには、スーパーエージェントを使用します。

イベントのデバウンスには、lodashを使用します。

superagentおよびlodash.debouncenpmを介してプロジェクトに追加するには、次のコマンドを実行します。

  1. npm install superagent@6.1.0 lodash.debounce@4.0.8

これで、Reactアプリケーションを実行できます。

  1. npm start

プロジェクトのエラーや問題を修正します。 そして、Webブラウザでlocalhost:3000にアクセスします。

動作するReactアプリケーションができたら、無限スクロール機能の構築を開始できます。

ステップ2—onscrollおよびloadApodsを実装する

無限スクロールには、2つの重要な部分が必要です。 1つは、ウィンドウのスクロール位置とウィンドウの高さをチェックして、ユーザーがページの下部に到達したかどうかを判断することです。 別の部分は、表示する追加情報の要求を処理します。

InfiniteSpace.jsファイルを作成することから始めましょう。

  1. nano src/InfiniteSpace.js

InfiniteSpaceコンポーネントを作成します。

src / InfiniteSpace.js
import React from 'react';
import request from 'superagent';
import debounce from 'lodash.debounce';

class InfiniteSpace extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      apods: [],
    };
  }

  render() {
    return (
      <div>
        <h1>Infinite Space!</h1>
        <p>Scroll down to load more!!</p>
      </div>
    )
  }
}

export default InfiniteSpace;

無限スクロールコンポーネントの核心は、ユーザーがページの一番下までスクロールしたかどうかを確認するonscrollイベントになります。 ページの下部に到達すると、イベントは追加のコンテンツを読み込もうとします。

イベントをバインドする場合、特にスクロールイベントにバインドする場合は、イベントをデバウンスすることをお勧めします。 デバウンスとは、関数が最後に呼び出されてから指定された時間が経過したときにのみ関数を実行することです。

デバウンスは、イベントが発生する頻度を制限することでユーザーのパフォーマンスを向上させ、イベントハンドラーから呼び出す可能性のあるサービスの負担を軽減するのにも役立ちます。

src / InfiniteSpace.js
class InfiniteSpace extends Component {
  constructor(props) {
    super(props);

    this.state = {
      apods: [],
    };

    window.onscroll = debounce(() => {
      const {
        loadApods
      } = this;

      if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
        loadApods();
      }
    }, 100);
  }

  // ...
}

このコードは、100ミリ秒のデバウンス反復を確立します。

loadApods関数は、superagentrequestからGET今日の天文学画像を使用します。

src / InfiniteSpace.js
class InfiniteSpace extends Component {
  constructor(props) {
    // ...
  }

  dayOffset = () => {
    let today = new Date();
    let day = today.setDate(-1 * this.state.apods.length);
    return new Date(day).toISOString().split('T')[0];
  }

  loadApods = () => {
    request
      .get('https://api.nasa.gov/planetary/apod?date=' + this.dayOffset() + '&api_key=DEMO_KEY')
      .then((results) => {
        const nextApod = {
          date: results.body.date,
          title: results.body.title,
          explanation: results.body.explanation,
          copyright: results.body.copyright,
          media_type: results.body.media_type,
          url: results.body.url
        };

        this.setState({
          apods: [
            ...this.state.apods,
            nextApod
          ]
        });
      });
  }

  render() {
    // ...
  }
}

dayOffset関数を使用して、前のAstronomy Picture oftheDayを計算します。

このコードは、APODからの応答をマップして、datetitleexplanationcopyrightmedia_type、およびurl

ロードされたデータは、コンポーネントの状態で配列に追加され、コンポーネントのrenderメソッドで繰り返されます。

2つの要素が連携して機能することを確認するために、応答をレンダリングしてみましょう。

class InfiniteSpace extends Component {
  // ...

  render() {
    return(
      <div>
        <h1>Infinite Space!</h1>
        <p>Scroll down to load more!!</p>

        {apods.map(apod => (
          <React.Fragment key={apod.date}>
            <hr />
            <div>
              <h2>{apod.title}</h2>
              {apod.media_type === 'image' &&
                <img
                  alt={`NASA APOD for {apod.date}`}
                  src={apod.url}
                  style={{
                    maxWidth: '100%',
                    height: 'auto'
                  }}
                />
              }
              {apod.media_type === 'video' &&
                <iframe
                  src={apod.url}
                  width='640'
                  height='360'
                  style={{
                    maxWidth: '100%'
                  }}
                ></iframe>
              }
              <div>{apod.explanation}</div>
              <div>{apod.copyright}</div>
            </div>
          </React.Fragment>
        ))}

        <hr />
      </div>
    );
  }
}

このコードは、APODのmedia_typeに応じて、imgまたはiframeのいずれかを表示します。

この時点で、Appコンポーネントを変更してInfiniteSpaceをインポートできます。 App.jsを開きます:

  1. nano src/App.js

そして、CreateReactAppによって生成されたコンテンツをInfiniteSpaceコンポーネントに置き換えます。

src / App.js
import React from 'react';
import InfiniteSpace from './InfiniteSpace';

function App() {
  return (
    <div className="App">
      <InfiniteSpace />
    </div>
  );
}

export default App;

この時点で、アプリケーションを再度実行できます。

  1. npm start

プロジェクトのエラーや問題を修正します。 そして、Webブラウザでlocalhost:3000にアクセスします。

Webページの高さを下にスクロールすると、onscrollイベントがloadApodsを起動する条件がトリガーされ、新しいAPODが画面に表示されます。

無限スクロール用のこれら2つの部品を配置すると、InfiniteSpaceコンポーネントの大部分が確立されます。 初期ロードとエラー処理を追加すると、より堅牢になります。

ステップ3—初期ロードとエラー処理を追加する

現在、InfiniteSpaceは、onscrollイベントの条件が満たされるまで、APODをロードしません。 APODをロードしたくない状況も3つあります。ロードするAPODがこれ以上ない場合、現在APODをロードしている場合、およびエラーが発生した場合です。 これらの問題に対処しましょう。

まず、InfiniteSpace.jsに再度アクセスします。

  1. nano src/InfiniteSpace.js

次に、componentDidMount()を初期ロードに使用します。

src / InfiniteSpace.js
class InfiniteSpace extends Component {
  constructor(props) {
    // ...
  }

  componentDidMount() {
    this.loadApods();
  }

  dayOffset = () => {
    // ...
  }

  loadApods = () => {
    // ...
  }

  render() {
    // ...
  }
}

errorhasMore、およびisLoadingを状態に追加して、エラーに対処し、不要なロードを制限します。

src / InfiniteSpace.js
class InfiniteSpace extends Component {
  constructor(props) {
    super(props);

    this.state = {
      error: false,
      hasMore: true,
      isLoading: false,
      apods: []
    };

    // ...
  }

  // ...
}

errorは最初はfalseに設定されています。 hasMoreは最初はtrueに設定されています。 また、isLoadingは最初はfalseに設定されています。

次に、onscrollに状態を適用します。

src / InfiniteSpace.js
class InfiniteSpace extends Component {
  constructor(props) {
    super(props);

    this.state = {
      error: false,
      hasMore: true,
      isLoading: false,
      apods: []
    };

    window.onscroll = debounce(() => {
      const {
        loadApods,
        state: {
          error,
          isLoading,
          hasMore
        }
      } = this;

      if (error || isLoading || !hasMore) return;

      if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
        loadApods();
      }
    }, 100);
  }

  // ...
}

このチェックは早期にベイルし、エラーが発生した場合、現在ロード中、またはロードする追加のAPODがない場合に、loadApodsが呼び出されないようにします。

次に、loadApodsに状態を適用します。

src / InfiniteSpace.js
class InfiniteSpace extends Component {
  // ...
  
  loadApods = () => { this.setState({ isLoading: true }, () => {
    request
      .get('https://api.nasa.gov/planetary/apod?date=' + this.dayOffset() + '&api_key=DEMO_KEY')
      .then((results) => {
        const nextApod = {
          date: results.body.date,
          title: results.body.title,
          explanation: results.body.explanation,
          copyright: results.body.copyright,
          media_type: results.body.media_type,
          url: results.body.url
        };

        this.setState({
          hasMore: (this.state.apods.length < 5),
          isLoading: false,
          apods: [
            ...this.state.apods,
            nextApod
          ],
        });
      })
      .catch((err) => {
        this.setState({
          error: err.message,
          isLoading: false
          });
      });
    });
  }

  // ...
}

このコードは、2番目の引数として渡されたコールバック関数setStateを使用します。 loadApodsメソッドでsetStateを最初に呼び出すと、isLoadingの値がtrueに設定され、コールバック関数で次のAPODが読み込まれ、[X157X ]