Reactで無限スクロールを実装する方法
序章
無限スクロールとは、ユーザーがページの下部に到達し、新しいコンテンツがフェッチおよびロードされるときに、ユーザーが比較的シームレスなエクスペリエンスでスクロールを継続できるようにすることです。 これは、より多くのコンテンツをロードする番号付きのページまたはボタンを使用する他のページネーションソリューションの代替手段です。
Instagramのようなアプリケーションで無限のスクロールに遭遇したかもしれません。 画像のフィードが表示され、下にスクロールすると、さらに多くの画像が表示され続けます。 彼らがあなたに与えるコンテンツを使い果たすまで、何度も何度も何度も。
このチュートリアルでは、無限スクロールを機能させる2つの重要な概念に触れます。ユーザーがページの下部に到達したことを検出し、表示するコンテンツの次のバッチをロードします。 これらの概念を使用して、天文学の写真やビデオの表示を作成します。
前提条件
このチュートリアルを完了するには、次のものが必要です。
- Node.jsのローカル開発環境。 Node.jsをインストールしてローカル開発環境を作成する方法に従ってください。
- このチュートリアルでは、 NASAの今日の天文学画像(APOD)APIを利用します。 デモンストレーションの目的で、リクエストに
DEMO_KEY
を使用しますが、多くのリクエストを生成する場合は、APIキーにサインアップすることをお勧めします。
このチュートリアルは、ノードv14.12.0、npm
v6.14.8、react
v16.13.1、superagent
v6.1.0、およびlodash.debounce
v2.7.1で検証されました。 。
ステップ1—プロジェクトの設定
create-react-app を使用してReactアプリを生成し、依存関係をインストールすることから始めます。
npx create-react-app react-infinite-scroll-example
新しいプロジェクトディレクトリに移動します。
cd react-infinite-scroll-example
APOD APIからデータをロードするには、スーパーエージェントを使用します。
イベントのデバウンスには、lodashを使用します。
superagent
およびlodash.debounce
をnpm
を介してプロジェクトに追加するには、次のコマンドを実行します。
npm install [email protected] [email protected]
これで、Reactアプリケーションを実行できます。
npm start
プロジェクトのエラーや問題を修正します。 そして、Webブラウザでlocalhost:3000
にアクセスします。
動作するReactアプリケーションができたら、無限スクロール機能の構築を開始できます。
ステップ2—onscroll
およびloadApods
を実装する
無限スクロールには、2つの重要な部分が必要です。 1つは、ウィンドウのスクロール位置とウィンドウの高さをチェックして、ユーザーがページの下部に到達したかどうかを判断することです。 別の部分は、表示する追加情報の要求を処理します。
InfiniteSpace.js
ファイルを作成することから始めましょう。
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
関数は、superagent
のrequest
から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からの応答をマップして、date
、title
、explanation
、copyright
、media_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
を開きます:
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;
この時点で、アプリケーションを再度実行できます。
npm start
プロジェクトのエラーや問題を修正します。 そして、Webブラウザでlocalhost:3000
にアクセスします。
Webページの高さを下にスクロールすると、onscroll
イベントがloadApods
を起動する条件がトリガーされ、新しいAPODが画面に表示されます。
無限スクロール用のこれら2つの部品を配置すると、InfiniteSpace
コンポーネントの大部分が確立されます。 初期ロードとエラー処理を追加すると、より堅牢になります。
ステップ3—初期ロードとエラー処理を追加する
現在、InfiniteSpace
は、onscroll
イベントの条件が満たされるまで、APODをロードしません。 APODをロードしたくない状況も3つあります。ロードするAPODがこれ以上ない場合、現在APODをロードしている場合、およびエラーが発生した場合です。 これらの問題に対処しましょう。
まず、InfiniteSpace.js
に再度アクセスします。
nano src/InfiniteSpace.js
次に、componentDidMount()
を初期ロードに使用します。
src / InfiniteSpace.js
class InfiniteSpace extends Component { constructor(props) { // ... } componentDidMount() { this.loadApods(); } dayOffset = () => { // ... } loadApods = () => { // ... } render() { // ... } }
error
、hasMore
、および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 ] が再度呼び出され、isLoading
がfalse
に設定されます。
チュートリアルでは、hasMore
はAPODの量を5
に制限するブールチェックです。 さまざまなシナリオで、APIは、ロードするコンテンツが他にあるかどうかを示すペイロードの一部として値を返す場合があります。
loadApods
でエラーが発生した場合、catch
ブロックでerror
がerr.message
に設定されます。
次に、render
に状態を適用します。
src / InfiniteSpace.js
class InfiniteSpace extends Component { // ... render() { const { error, hasMore, isLoading, apods } = this.state; return ( <div> {/* ... React.Fragment ... */} {error && <div style={{ color: '#900' }}> {error} </div> } {isLoading && <div>Loading...</div> } {!hasMore && <div>Loading Complete</div> } </div> ); } ]
これで、error
、isLoading
、およびhasMore
のメッセージが表示されます。
すべてのピースを組み合わせると、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 = { 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); } componentDidMount() { this.loadApods(); } dayOffset = () => { let today = new Date(); let day = today.setDate(-1 * this.state.apods.length); return new Date(day).toISOString().split('T')[0]; } 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 }); }); }); } render() { const { error, hasMore, isLoading, apods } = this.state; return ( <div style={{ padding: 10 }}> <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 /> {error && <div style={{ color: '#900' }}> {error} </div> } {isLoading && <div>Loading...</div> } {!hasMore && <div>Loading Complete</div> } </div> ); } } export default InfiniteSpace;
最後に、アプリケーションを再度実行します。
npm start
プロジェクトのエラーや問題を修正します。 そして、Webブラウザでlocalhost:3000
にアクセスします。
下にスクロールすると、アプリケーションは5つのAPODをフェッチして表示します。 無限スクロールのすべての要素が一緒になりました。
結論
このチュートリアルでは、Reactアプリケーションに無限スクロールを実装しました。 無限スクロールは、初期読み込み時間を長くすることなく、エンドユーザーに多くの情報を提示することに対処するための最新のソリューションの1つです。
プロジェクトのフッターにユーザーが到達したいコンテンツがある場合、無限にスクロールするとユーザーエクスペリエンスが低下する可能性があります。
プロジェクトのニーズに最適なこの機能を提供する他のライブラリもあります。
Reactの詳細については、 React.js シリーズのコーディング方法をご覧になるか、Reactトピックページで演習やプログラミングプロジェクトを確認してください。