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.debouncev2.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トピックページで演習やプログラミングプロジェクトを確認してください。