使用交叉观察器在 React 中实现无限滚动
大家好,
几天前,我偶然发现了 React 中无限滚动的用例。为此,我使用了 Intersection Observer,并找到了在无限滚动中实现它的不同方法。
在深入探讨之前,我们先来更好地理解一下问题描述。假设有一个 API,它提供用户列表及其一些基本信息。我们的任务是在卡片中显示所有用户的列表。很简单吧?
现在,假设有成千上万的用户,并且我们使用的 API 是分页的。在这种情况下,有两种方法可以使用我们的分页 API:
- 使用下一个/上一个按钮浏览不同的页面
- 使用无限滚动
正如文章标题所说,我们将采用第二种方法。😅
现在,让我们看看如何做?
- 我们将调用我们的 API 来获取前 25 个结果。
- 一旦用户滚动列表并到达最后一个元素,我们将进行另一次 API 调用并在视图中提取下一组用户。
这样,即使用户继续滚动,他们也始终会看到用户列表,直到到达最后。
在进入实现部分之前,让我先简单介绍一下 Intersection Observer
什么是交叉口观察器?
交叉观察器是一种浏览器 API,它提供了一种异步观察或检测两个元素相互之间可见性的方法。
根据 MDN,此 API 主要用于执行与可见性相关的任务,包括图像的延迟加载和实现“无限滚动”网站,其中滚动时会加载和呈现越来越多的内容。
您可以在此处查看交叉口观察器的详细信息。
实现无限滚动
对于无限滚动,我们将使用开源RandomUserAPI。
为了进行基本的项目设置,我使用create-react-app创建了一个简单的 React 项目,并添加了Tailwind CSS。此外,为了调用 API,我还在同一个项目中添加了axios 。
我将实施过程分为以下两个步骤 -
1.调用API,存储并展示数据。
完成基本设置后,让我们看一下代码的第一个版本,其中我们调用用户 API 来获取用户列表。
// app.js
import axios from 'axios';
import { useEffect, useState } from 'react';
const TOTAL_PAGES = 3;
const App = () => {
const [loading, setLoading] = useState(true);
const [allUsers, setAllUsers] = useState([]);
const [pageNum, setPageNum] = useState(1);
const callUser = async () => {
setLoading(true);
let response = await axios.get(
`https://randomuser.me/api/?page=${pageNum}&results=25&seed=abc`
);
setAllUsers(response.data.results);
setLoading(false);
};
useEffect(() => {
if (pageNum <= TOTAL_PAGES) {
callUser();
}
}, [pageNum]);
const UserCard = ({ data }) => {
return (
<div className='p-4 border border-gray-500 rounded bg-white flex items-center'>
<div>
<img
src={data.picture.medium}
className='w-16 h-16 rounded-full border-2 border-green-600'
alt='user'
/>
</div>
<div className='ml-3'>
<p className='text-base font-bold'>
{data.name.first} {data.name.last}
</p>
<p className='text-sm text-gray-800'>
{data.location.city}, {data.location.country}
</p>
<p className='text-sm text-gray-500 break-all'>
{data.email}
</p>
</div>
</div>
);
};
return (
<div className='mx-44 bg-gray-100 p-6'>
<h1 className='text-3xl text-center mt-4 mb-10'>All users</h1>
<div className='grid grid-cols-3 gap-4'>
{allUsers.length > 0 &&
allUsers.map((user, i) => {
return (
<div key={`${user.name.first}-${i}`}>
<UserCard data={user} />
</div>
);
})}
</div>
{loading && <p className='text-center'>loading...</p>}
</div>
);
};
export default App;
代码非常简单。在callUser
函数中,我们调用 API 并将结果存储在状态中。下面,我们使用卡片组件allUsers
显示数组中的每个用户。allUsers
UserCard
您将看到组件顶部定义了一个const 变量TOTAL_PAGES
,用于限制我们在整个应用程序中需要遍历的页面总数。在实际应用中,不需要这样做,因为 API 会提供可用页面总数的详细信息。
另外,你可能已经注意到,我们定义了一个状态来存储页码,但到目前为止,还没有正确使用它。这是因为我们想从交叉点观察器中更改这个页码。
2. 添加交叉口观察器并增加页码
要实现无限滚动,我们需要在列表的最后一个元素对用户可见时增加页码计数。这将由交叉观察器完成。
我们的交叉观察者将观察最后一个元素是否可见,如果可见,我们将页码增加 1。由于我们的 useEffect 将在页码发生变化时运行,因此 API 将被调用,因此我们将获得更多用户列表。
理解了这个逻辑之后,我们来看看工作代码——
// App.js
const App = () => {
const [loading, setLoading] = useState(true);
const [allUsers, setAllUsers] = useState([]);
const [pageNum, setPageNum] = useState(1);
const [lastElement, setLastElement] = useState(null);
const observer = useRef(
new IntersectionObserver(
(entries) => {
const first = entries[0];
if (first.isIntersecting) {
setPageNum((no) => no + 1);
}
})
);
const callUser = async () => {
setLoading(true);
let response = await axios.get(
`https://randomuser.me/api/?page=${pageNum}&results=25&seed=abc`
);
let all = new Set([...allUsers, ...response.data.results]);
setAllUsers([...all]);
setLoading(false);
};
useEffect(() => {
if (pageNum <= TOTAL_PAGES) {
callUser();
}
}, [pageNum]);
useEffect(() => {
const currentElement = lastElement;
const currentObserver = observer.current;
if (currentElement) {
currentObserver.observe(currentElement);
}
return () => {
if (currentElement) {
currentObserver.unobserve(currentElement);
}
};
}, [lastElement]);
const UserCard = ({ data }) => {
return (
<div className='p-4 border border-gray-500 rounded bg-white flex items-center'>
<div>
<img
src={data.picture.medium}
className='w-16 h-16 rounded-full border-2 border-green-600'
alt='user'
/>
</div>
<div className='ml-3'>
<p className='text-base font-bold'>
{data.name.first} {data.name.last}
</p>
<p className='text-sm text-gray-800'>
{data.location.city}, {data.location.country}
</p>
<p className='text-sm text-gray-500 break-all'>
{data.email}
</p>
</div>
</div>
);
};
return (
<div className='mx-44 bg-gray-100 p-6'>
<h1 className='text-3xl text-center mt-4 mb-10'>All users</h1>
<div className='grid grid-cols-3 gap-4'>
{allUsers.length > 0 &&
allUsers.map((user, i) => {
return i === allUsers.length - 1 &&
!loading &&
pageNum <= TOTAL_PAGES ? (
<div
key={`${user.name.first}-${i}`}
ref={setLastElement}
>
<UserCard data={user} />
</div>
) : (
<UserCard
data={user}
key={`${user.name.first}-${i}`}
/>
);
})}
</div>
{loading && <p className='text-center'>loading...</p>}
{pageNum - 1 === TOTAL_PAGES && (
<p className='text-center my-10'>♥</p>
)}
</div>
);
};
让我们深入了解代码。
我们定义了相交观察器并将其存储在 const 中observer
。相交观察器有一个回调函数,该函数接受所有相交对象的数组。但由于我们只传递最后一个元素,因此我们始终会检查该数组的第 0 个元素。如果该元素相交且可见,我们将增加页码。
我们添加了一个状态lastElement
并将其初始化为null
。在页面内部,我们将把数组的最后一个元素传递给此状态。
因此,当状态值lastElement
发生变化时,会调用另一个 useEffect(使用lastElement
依赖项数组)。在这个 useEffect 中,如果我们获取到 lastElement 的值,我们会将该元素传递给交集观察器进行观察。观察器会检查该元素的交集,并在发生这种情况时增加页面计数。
随着页码的变化,API 将被调用并获取更多用户。请注意我们所做的细微修改,以便将这些新用户添加到现有状态中并避免重复。
该应用程序将毫不费力地运行,您现在可以看到无限滚动的实际效果!🥁
就这样吧!如果你想查看完整的代码,可以访问我的Github 仓库。
非常感谢你阅读这篇文章。欢迎留言告诉我你的想法。如果你喜欢我的文章,也可以在Twitter上联系我,或者请我喝杯咖啡。
*快乐编码并继续学习 🙌 *
文章来源:https://dev.to/hey_yogini/infinite-scrolling-in-react-with-intersection-observer-22fh