在 React 中构建无限滚动组件
介绍
应用程序组件
无限滚动
与我联系🚀
结论
介绍
我们在应用程序和网页中经常看到无限滚动,尤其是在社交媒体上,它们只要求我们滚动。虽然盲目滚动并不好,但构建自己的无限滚动功能非常棒。作为开发者,我们应该尝试重新创建我们在浏览网页时看到的组件。在实现某些组件时,它可以挑战你学习更多知识,并打破常规思维。
此外,如果您想在应用中实现无限滚动,可以按照指南自行创建。您可以添加自己的代码来优化滚动效果。
在本文中,我们将从头开始构建一个无限滚动组件。它将涵盖以下主题:
- 环境设置
- 构建组件
- 添加 CSS
- 优化无限滚动
现在,让我们开始吧。
环境设置
我们将使用 CRA 来创建基本的 React 应用程序。你可以运行以下命令来执行此操作:
npx create-react-app infinite-scroll
如果您希望使用 Vite 或 NextJS,也可以。除了一些细微的变化外,其他内容保持不变。
注意:要运行此命令,您需要预先安装 NodeJS。此外,请从 CRA 中删除一些不必要的样板代码。
我们需要一个依赖项来从 API 获取数据。设置好 React 之后,我们可以使用以下命令安装 Axios:
npm install axios
现在,我们准备创建组件。
应用程序组件
我们将构建一个组件,用于从Tmdb API获取热门电影数据。该组件是免费的,您可以从他们的网站获取 API 密钥。我们先构建一个获取数据的组件,然后再添加无限滚动功能。
以下是应用程序组件的代码:
App.js
import "./App.css";
import { useState, useEffect } from "react";
import axios from "axios";
import { MovieCard } from "./MovieCard";
function App() {
const [page, setPage] = useState(1); // for number of page in tmdb
const [data, setData] = useState([]); // storing the fetched data
const [loading, setLoading] = useState(false); // for setting loading state
// fetching and stroring the data in the state
const fetchMovie = async () => {
const URL = `https://api.themoviedb.org/3/movie/popular?language=en-US&page=${page}`;
const data = await axios.get(URL, {
headers: {
Authorization:
"Bearer API KEY",
Accept: "application/json",
},
});
setData((prevData) => [...prevData, ...data.data.results]); // we are going to add the new data to current data.
setLoading(false);
};
// useEffecte for invoking the function at the start
useEffect(() => {
fetchMovie();
}, [page]);
return (
<div className="App">
<header className="App-header">
Popular movies according to Tmdb
<div className="movieCardContainer">
{data.length > 1 &&
data.map((item) => {
return (
<MovieCard
key={item.id}
title={item.original_title}
description={item.overview}
rating={item.vote_average}
imageURL={item.poster_path}
/>
);
})}
{loading && <h1>Loading....</h1>}
</div>
</header>
</div>
);
}
export default App;
您几乎可以理解代码,我们在其中获取数据并将其作为 prop 传递到 MovieCard 组件中。
创建一个 MovieCard.js 组件来显示每部电影的信息。
MoveCard.js
import React from "react";
export const MovieCard = ({ title, description, imageURL, rating }) => {
const imagePath = `https://image.tmdb.org/t/p/w500${imageURL}`; // poster image path URL
return (
<div className="movieCard">
<img src={imagePath} height={400} />
<div className="movieInfo">
<h3>{title}</h3>
<p>{description}</p>
<p>{rating.toFixed(1)}⭐</p>
</div>
</div>
);
};
以下是该应用程序的 CSS:
应用程序.css
.App {
text-align: center;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 1em;
font-size: calc(10px + 2vmin);
color: white;
}
.movieCardContainer{
margin-top: 1em;
display: flex;
flex-direction: column;
gap: 1em;
width: 60%;
max-width: 800px;
}
.movieCard{
display: flex;
}
.movieInfo{
margin-left: 1em;
text-align: left;
}
p{
font-size: 18px;
}
无限滚动
现在,我们先来了解如何构建无限滚动。为此,我们将查看滚动条的位置。当滚动条位置刚好位于页面末尾上方时,我们将状态设置loading
为 true。
我们将添加另一个useEffect
函数,将page
状态加 1。一旦页码更新,初始的、以该页面为依赖项的 useEffect 就会触发。这将调用该fetchMovie()
函数来获取数据。
添加 EventListner 来滚动
首先,我们要添加甚至监听以了解滚动条位置何时发生变化。
window.addEventListener("scroll", handleScroll);
滚动手柄
当滚动发生时,我们会检查滚动条的当前位置是否刚好位于网页底部上方(即整个垂直可滚动区域)。如果是,则将加载状态更改为 true。
const handleScroll = () => {
if (document.body.scrollHeight - 300 < window.scrollY + window.innerHeight) {
setLoading(true);
}
};
- scrollHeight :该属性返回内容的总高度,包括屏幕上不可见的部分。因此,它将是可滚动区域的总高度。
- scrollY:此属性返回文档从顶部垂直滚动的像素数。因此,它将是已滚动的区域。
- innerHeight:该属性返回浏览器 Windows 内容区域的高度。它将作为滚动条的宽度。它被添加到 scrollY 中,以便获取内容时发生,而不是在内容被传递时发生。## useEffect
成功更改 状态后loading
,我们可以实现 useEffect 来增加页码。这样,就可以获取电影数据了。
useEffect(() => {
if (loading == true) {
setPage((prevPage) => prevPage + 1);
}
}, [loading]);
// other useEffect that we already implemented
useEffect(() => {
fetchMovie();
}, [page]);
优化 eventListner
由于 scroll 在滚动过程中会多次触发 handleScroll,这会导致函数不必要的多次调用。我们可以为函数添加 debounce 函数,让它在调用之前等待一段时间。
// debounce function
function debounce(func, delay) {
let timeoutId;
return function (...args) {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
func(...args);
}, delay);
};
}
// adding debounce to the eventListner
window.addEventListener("scroll", debounce(handleScroll, 500));
以下是App.js的完整代码:
import "./App.css";
import { useState, useEffect } from "react";
import axios from "axios";
import { MovieCard } from "./MovieCard";
function App() {
const [page, setPage] = useState(1);
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const fetchMovie = async () => {
const URL = `https://api.themoviedb.org/3/movie/popular?language=en-US&page=${page}`;
const data = await axios.get(URL, {
headers: {
Authorization:
"Bearer API KEY",
Accept: "application/json",
},
});
setData((prevData) => [...prevData, ...data.data.results]);
setLoading(false);
};
useEffect(() => {
fetchMovie();
}, [page]);
const handleScroll = () => {
if (
document.body.scrollHeight - 300 <
window.scrollY + window.innerHeight
) {
setLoading(true);
}
};
function debounce(func, delay) {
let timeoutId;
return function (...args) {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
func(...args);
}, delay);
};
}
window.addEventListener("scroll", debounce(handleScroll, 500));
useEffect(() => {
if (loading == true) {
setPage((prevPage) => prevPage + 1);
}
}, [loading]);
return (
<div className="App">
<header className="App-header">
Popular movies according to Tmdb
<div className="movieCardContainer">
{data.length > 1 &&
data.map((item) => {
return (
<MovieCard
key={item.id}
title={item.original_title}
description={item.overview}
rating={item.vote_average}
imageURL={item.poster_path}
/>
);
})}
{loading && <h1>Loading....</h1>}
</div>
</header>
</div>
);
}
export default App;
这是演示该应用程序工作原理的 GIF。
与我联系🚀
让我们保持联系,随时了解科技、创新及其他领域的最新资讯!
Twitter
LinkedIn
此外,如果您有兴趣,我愿意撰写自由撰稿文章,请通过电子邮件或社交媒体联系我。
结论
在 React 中构建无限滚动组件可以带来非常有益的体验。它不仅能加深你对滚动工作原理的理解,还能教会你状态管理、事件监听器以及诸如去抖动之类的优化技巧。按照本指南,你现在已经掌握了基本的无限滚动设置,并可以根据自己的需求进行自定义和改进。
无论您要显示电影数据、博客文章还是其他任何内容,此组件都能为您提供坚实的基础。请记住,关键在于通过精心管理用户滚动时数据的获取时间和方式,确保流畅的用户体验。祝您编码愉快!
文章来源:https://dev.to/surajondev/building-an-infinite-scroll-component-in-react-1ljb