React 无限滚动教程:使用和不使用库
本教程最初发布于https://www.devaradise.com/react-infinite-scroll-tutorial
无限滚动是一种现代网页和应用程序设计理念,它会随着用户向下滚动页面而持续加载内容。它改变了分页的功能。
如果您有大量数据需要加载,并且不希望用户点击页码来查看更多数据,那么实现无限滚动是一个不错的选择。它可以提升应用程序的用户体验。
作为开发者,我们可以在任何应用程序中实现无限滚动,包括 React 应用程序。React 无限滚动可以通过两种方式实现:无需库即可手动实现,以及使用无限滚动库。
在这篇文章中,我将向您展示并解释如何在 React 项目中实现无限滚动,使用和不使用库都是如此。两种方法各有优缺点。
在开始教程之前,请确保你已经知道如何使用 create-react-app 样板代码初始化一个 React 应用。因为我不会在这里讲解 React 的基础教程,所以我假设你已经理解了。
在本教程中,我们将使用 React 函数式组件和钩子。我们还将使用React-lab来托管演示示例,并使用该项目架构来管理项目文件。
如何在不使用库的情况下实现无限滚动
如果你想让你的 React 项目尽可能轻量,那么不使用任何库来实现 React 无限滚动是最好的选择。如果你需要进行一些自定义,这也是最好的选择。
就我个人而言,我会选择这种方法在我的 React 应用上实现无限滚动。我认为它不需要写太多代码和逻辑。
我们只需要一些状态、一个滚动事件监听器、一个 API 调用服务以及加载数据和放置一些逻辑的函数。
创建组件
假设我们要创建一个实现无限滚动的用户列表页面。因此,我们需要一个组件来实现它。
import React, { useState } from "react";
export default function InfiniteScrollNoLibrary() {
const [userList, setUserList] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [noData, setNoData] = useState(false);
return (
<div>
<div className="section">
{userList.map((user, i) =>
(
<div className="box m-3 user" key={i}>
<img src={user.avatar} alt={user.first_name}/>
<div className="user-details">
<strong>Email</strong>: {user.email}<br/>
<strong>First Name</strong>: {user.first_name}<br/>
<strong>Last Name</strong>: {user.last_name}<br/>
</div>
</div>
)
)}
{loading ? <div className="text-center">loading data ...</div> : "" }
{noData ? <div className="text-center">no data anymore ...</div> : "" }
</div>
</div>
);
}
我还没有添加业务逻辑和事件监听器。我先解释一下状态和标记。
要手动实现无限滚动,我们至少需要 4 种状态:
userList
用于存储来自 API 的用户数据数组。默认为空数组。page
计算需要加载的用户列表页面。这有助于我们避免加载和添加相同的数据到列表中。loading
在调用 API 时给出加载状态。noData
当没有数据时给出无数据状态并停止 API 调用。
正如您在上面的代码中看到的,userList
状态将使用 JSX 标记进行循环。每当状态有值map
时,还会添加“正在加载...”和“不再有数据...”文本。loading
noData
true
创建 API 调用服务
在我向组件添加一些逻辑之前,我首先创建一个用于调用用户数据的服务。
实际上,你可以直接在组件中调用 API,而无需创建服务。但我个人更喜欢将其与组件分离。你可以在我的React 项目结构文章中了解原因。
import axios from 'axios';
export default {
getList: async function(page) {
try {
let url;
if(page!=null & page > 1) {
url ="https://reqres.in/api/users?per_page=2&page="+page;
} else {
url = "https://reqres.in/api/users?per_page=2";
}
const response = await axios.get(url);
return response.data;
} catch(error) {
throw error;
}
}
}
上面的 getList 函数接受一个page
参数,用于根据插入的页码动态更改 URL 字符串。对于虚拟数据,我使用了resreq.in用户 API。
向组件添加一些逻辑
创建服务后,我们现在需要在组件中使用它并添加一些逻辑。完整的组件代码请见下方。之后我会进行解释。
import React, { useState, useEffect } from "react";
import UserService from 'services/UserService';
export default function InfiniteScrollNoLibrary() {
const [userList, setUserList] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [noData, setNoData] = useState(false);
window.onscroll = () => {
if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
if(!noData) {
loadUserList(page);
}
}
}
useEffect(() => {
loadUserList(page);
}, []);
const loadUserList = (page) => {
setLoading(true);
setTimeout(() => {
UserService.getList(page)
.then((res) => {
const newPage = page + 1;
const newList = userList.concat(res.data);
setUserList(newList);
setPage(newPage);
if(res.data.length===0)
setNoData(true);
})
.catch((err) => {
console.log(err);
})
.finally(() =>{
setLoading(false);
})
}
,1500);
}
return (
<div>
<div className="section">
{userList.map((user, i) =>
(
<div className="box m-3 user" key={i}>
<img src={user.avatar} alt={user.first_name}/>
<div className="user-details">
<strong>Email</strong>: {user.email}<br/>
<strong>First Name</strong>: {user.first_name}<br/>
<strong>Last Name</strong>: {user.last_name}<br/>
</div>
</div>
)
)}
{loading ? <div className="text-center">loading data ...</div> : "" }
{noData ? <div className="text-center">no data anymore ...</div> : "" }
</div>
</div>
);
}
首先,我们导入UserService
并useEffect
挂载组件。稍后我们将在 API 调用函数中使用它们。
上述组件中最重要的代码位于第 11 至 17 行。
window.onscroll = () => {
if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
if(!noData) {
loadUserList(page);
}
}
}
这是一个监听用户滚动页面的函数。我在里面添加了一个逻辑:“如果用户滚动到页面底部,并且noData
state 为 false,则加载用户列表”。
当用户刚进入页面但尚未滚动时,我们会在useEffect
钩子内加载用户列表。因此,用户数据仍然处于加载状态。
useEffect(() => {
loadUserList(page);
}, []);
现在,看一下该loadUserList
函数。
const loadUserList = (page) => {
setLoading(true);
setTimeout(() => {
UserService.getList(page)
.then((res) => {
const newList = userList.concat(res.data);
setUserList(newList);
const newPage = page + 1;
setPage(newPage);
if(res.data.length===0)
setNoData(true);
})
.catch((err) => {
console.log(err);
})
.finally(() =>{
setLoading(false);
})
}
,1500);
}
首先,我们将加载状态设置为true
在调用 API 时显示“正在加载...”文本。我在这里使用 setTimeout 函数只是为了延迟 API 调用,以便能够看到加载状态。您不必在代码中使用它。
在第 4 行,我调用 UserService 中的 getList 函数并将其传递page
给它。如果 API 请求成功,来自 API 的新用户数据将被添加到当前用户列表中(第 6-7 行)。
我们还需要为用户再次滚动时调用的 API 设置新的page
状态。你可以在第 9-10 行看到它。
最后,我们创建一个条件来设置noData
State。如果 API 响应为空数组,则表示没有其他数据需要加载。因此,我们将noData
State 设置为true
。
如果 API 请求返回错误,请在catch
部分中捕获该错误。在本例中,我只是通过 console.log 来记录错误。并在finally
部分中将loading
状态设置为false
“再次”,因为请求已结束。
就是这样。现在你可以自己练习一下了。要查看无需库的无限滚动的现场演示,请点击下面的链接。
如何实现无限滚动react-infinite-scroller
如果你不想手动实现 React 无限滚动,你仍然可以使用库来实现。市面上有很多用于实现 React 无限滚动的库。
如果您希望代码更简洁,并且希望有一些选项可以轻松自定义,那么使用无限滚动库是最佳选择。大多数 React 无限滚动库都比我之前介绍的手动实现拥有更多的选项和功能。
在本教程中,我使用它react-infinite-scroller
,因为它简单易行。事不宜迟,让我们看看如何在你的 React 项目中使用它。
安装并导入 react-infinite-scroller
首先,你应该使用 npm 将 react-infinite-scroller 安装到你的项目中
npm i react-infinite-scroller
要在组件中使用它,只需像这样导入它。
import InfiniteScroll from 'react-infinite-scroller'
在组件中使用 InfiniteScroll
这是完整的组件代码。我在下面进行解释。
import React, { useState } from 'react'
import InfiniteScroll from 'react-infinite-scroller'
import UserService from 'services/UserService';
export default function InfiniteScrollerWithReactInfiniteScroller() {
const [userList, setUserList] = useState([]);
const [hasMoreItems, setHasMoreItems] = useState(true);
const loadUserList = (page) => {
setTimeout(() => {
UserService.getList(page)
.then((res) => {
const newList = userList.concat(res.data);
setUserList(newList);
if(res.data.length===0) {
setHasMoreItems(false);
} else {
setHasMoreItems(true);
}
})
.catch((err) => {
console.log(err);
})
}, 1500)
}
return (
<div>
<div className="section">
<InfiniteScroll
threshold={0}
pageStart={0}
loadMore={loadUserList}
hasMore={hasMoreItems}
loader={<div className="text-center">loading data ...</div>}>
{userList.map((user, i) =>
(
<div className="box m-3 user" key={i}>
<img src={user.avatar} alt={user.first_name}/>
<div className="user-details">
<strong>Email</strong>: {user.email}<br/>
<strong>First Name</strong>: {user.first_name}<br/>
<strong>Last Name</strong>: {user.last_name}<br/>
</div>
</div>
)
)}
</InfiniteScroll>
{hasMoreItems ? "" : <div className="text-center">no data anymore ...</div> }
</div>
</div>
)
}
如你所见,使用库可以减少需要编写的状态和逻辑。我们只需要userList
状态来存储用户数据,并将hasMoreItems
其传递给<InfiniteScroll/>
。page
和loading
状态将由 处理react-infinite-scroll
。
在loadUserList
函数中,我们使用了之前手动实现的 UserService。当 API 请求成功时,我们只需要设置新的用户列表(第 14-15 行)并设置hasMoreItems
状态(第 17-21 行)。
大多数逻辑都是通过<InfiniteScroll/>
循环来处理的userList
。
<InfiniteScroll
threshold={0}
pageStart={0}
loadMore={loadUserList}
hasMore={hasMoreItems}
loader={<div className="text-center">loading data ...</div>}>
{userList.map((user, i) =>
(
<div className="box m-3 user" key={i}>
<img src={user.avatar} alt={user.first_name}/>
<div className="user-details">
<strong>Email</strong>: {user.email}<br/>
<strong>First Name</strong>: {user.first_name}<br/>
<strong>Last Name</strong>: {user.last_name}<br/>
</div>
</div>
)
)}
</InfiniteScroll>
如你所见,上面 i 用到了一些属性InfiniteScroll
。以下是具体解释。
threshold
是触发新列表加载的页面底部与窗口视口底部之间的距离 - 默认为250
。但我将其设置为 0。pageStart
是初始列表对应的页码,默认为,0
表示第一次加载时,loadMore
会用 来调用1
。loadMore(pageToLoad)
当用户向下滚动时,我们需要加载新列表,就会调用该方法。该值应该是一个函数。它会将page
数字传递给该值。hasMore
是一个布尔值,表示是否还有更多项目需要加载。如果 ,则删除事件监听器false
。loader
InfiniteScroll
是加载项目时显示的加载器元素 - 您可以使用 InfiniteScroll.setDefaultLoader(loader); 为所有组件设置默认加载器
要使用更多属性,您可以查看此处的文档。如果您想观看使用 react-infinite-scoller 实现无限滚动的现场演示,请点击下方链接。
就这样。希望对你有用。
编码愉快!
文章来源:https://dev.to/syakirurahman/react-infinite-scroll-tutorial-with-and-without-a-library-1abg