无限滚动是一种流行的交互模式,允许用户在向下滚动页面时持续加载内容。这意味着一个应用程序获取一小块数据,并在用户滚动时继续获取更多数据。
这种模式最常见的使用案例之一是在大规模的社交媒体网站上看到的,如Instagram和Twitter。与在初始加载时获取网站的全部数据相比,这提供了重大的性能改进。
在这篇文章中,我们将学习如何使用React Query的useInifiniteQuery() Hook在React应用程序中建立一个类似Instagram的无限滚动feed。
React Query的前提条件和演示
本文假设你对React组件、常见的Hooks如 useState()和 [useEffect()](https://blog.logrocket.com/guide-to-react-useeffect-hook/),并熟悉向React项目添加npm包。
如果你是React Query的新手,你可以查看React Query的新内容,以了解更多关于它和它的好处。然而,我们将在本文中只讨论useInfiniteQuery() Hook。
要预览这个项目的全文,请访问这个CodeSandbox链接,查看源代码和演示。

为什么使用React Query?
React是一个无观点的JavaScript库,可以构建交互式和可扩展的Web应用。然而,这种无主见的性质也可以作为一把双刃剑,因为它没有内置的数据获取解决方案。
虽然你可以实现你自己的数据获取机制,但React Query提供了一种更简单、更有效的方式来管理Hooks形式的异步服务器状态。
这些Hooks还具有缓存响应数据、扣除多个请求和更多性能优化的额外好处。
这个库中最常用的一些Hooks是useQuery() Hook,它从API中获取数据,以及useMutation() Hook,它创建、更新和删除服务器数据。
useInfiniteQuery() 钩子只是useQuery() 钩子的一个修改版本,提供了无限滚动的功能。
了解useInfiniteQuery() 钩子
在进入项目之前,让我们花点时间了解一下useInfiniteQuery() Hook是如何工作的以及如何使用它。这个Hook需要两个强制参数:查询键和查询函数,以及一个可选的options 对象。
这个Hook返回的值和函数可以检索获取的数据,检查查询的状态(如error,loading,fetching, 或idle ),并检查是否有更多的页面或其他信息发送到无限滚动器组件上。
如需详细解释 [useInfiniteQuery()](https://react-query.tanstack.com/reference/useInfiniteQuery)Hook的详细解释,请参见官方API参考文档。
现在,让我们在接下来的几节中探讨这个Hook的实际用法。
建立useInfiniteQuery() 项目
要与这个项目一起编码,你可以访问这个CodeSandbox链接来获得预装了所有依赖项的启动文件,或者通过运行这个命令在本地机器上使用create-react-app 工具创建一个新的React应用。
npx create-react-app infinite-scroll
如果你选择在本地机器上创建React应用,请使用下面的命令安装React Query和无限滚动器组件。
npm install react-query react-infinite-scroller
#or
yarn add react-query react-infinite-scroller
虽然React Query可以帮助你获取数据,但提供无限滚动器组件的UI实现则取决于你。这就是为什么我们要使用react-infinite-scroller 库。
配置React Query
react-query 在我们开始使用React Query的Hooks之前,我们必须从QueryClient 和QueryClientProvider ,并将其包裹在index.js 文件内的<App /> 组件上。
这可以确保React应用程序中的所有组件都可以访问Hooks和缓存。
#index.js
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import ReactDOM from "react-dom";
import App from "./App";
const queryClient = new QueryClient();
ReactDOM.render(
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevTools />
</QueryClientProvider>,
document.getElementById("root")
);
这段代码渲染了我们的登陆页面,我们的图片最终将驻留在那里。
在上面的例子中,我们还导入了React Query Devtools,这是一个方便的工具,内置了react-query ,用于监控网络请求和其他查询细节。
就这样,我们完成了将React Query集成到我们的React项目中。就是这么简单。
使用Lorem Picsum API
为了显示无限滚动feed的图片,我们将使用Lorem Picsum API来获取一个图片数组和JSON格式的信息。更具体地说,我们将使用以下API端点。
https://picsum.photos/v2/list?page=1&limit=10
使用limit 查询参数,我们可以将每次API调用获取的图片数量设置为10 。这样一来,最初会检索10张图片,然后在用户接近馈送的终点时,继续检索10张图片。
通过增加page 查询参数,我们可以获取下一组图片。最初,page 查询参数被设置为1 ,从第一页开始。
上述端点的响应看起来是这样的。
[ { "id": "0", "author": "Alejandro Escamilla", "width": 5616, "height": 3744, "url": "https://unsplash.com/photos/yC-Yzbqy7PY", "download_url": "https://picsum.photos/id/0/5616/3744" }, { ... }, { ... }]
还值得注意的是,这个API端点总共提供了1000张图片。因此,使用每次API调用10张图片的限制,我们可以期待有100页的图片。
构建和风格化一个PostCard 组件
让我们做一个简单的React组件来显示图片和它的作者。首先,在src 目录下创建一个文件夹,名为components 。在这个components 文件夹中,创建一个名为PostCard.jsx 的新文件并粘贴以下代码。
// components/PostCard.jsx
const PostCard = ({ post }) => {
return (
<div className="post-card">
<h4>{post.author}</h4>
<img src={post.download_url} alt={post.author} />
</div>
);
};
export default PostCard;
这个组件接受一个名为post 的道具,并使用author 和download_url 属性来显示作者的名字和图片。为了使这个组件具有风格,将下面给出的CSS附加到App.css 文件中。
// App.css
.post-card {
display: flex;
flex-direction: column;
border: 1px solid #dbdbdb;
margin-bottom: 1.5rem;
}
.post-card h4 {
background: #fafafa;
padding: 0.5rem;
}
.post-card img {
height: 300px;
width: 500px;
object-fit: cover;
}
现在,PostCard 组件已经准备好在App.js 文件中使用。现在让我们继续从API中获取数据。
实现无限滚动
为了开始在我们的应用程序中实现无限滚动,让我们制作一个名为fetchPosts() 的函数,向端点发出GET 请求,并根据页码和限制检索一个帖子阵列。
const fetchPosts = async ({ pageParam = 1 }) => {
const response = await fetch(
`https://picsum.photos/v2/list?page=${pageParam}&limit=10`
);
const results = await response.json();
return { results, nextPage: pageParam + 1, totalPages: 100 };
};
这个函数也接受React Query在调用这个函数时自动传递的pageParam 参数。在这种情况下,pageParam 是页面编号。
由于我们使用的API没有在响应中提供总页数和下一个页数,让我们返回一个带有这些属性的自定义对象,因为我们知道下一个页数将是当前页数加1,而总页数将是100。
现在,从react-query 中导入useInfiniteQuery() Hook,并以这种方式使用它。
const { data, isLoading, isError, hasNextPage, fetchNextPage } =
useInfiniteQuery("posts", fetchPosts, {
getNextPageParam: (lastPage, pages) => {
if (lastPage.nextPage < lastPage.totalPages) return lastPage.nextPage;
return undefined;
},
});
将"posts" 作为查询键,将fetchPosts 函数作为查询函数。作为第三个参数,传递一个包含getNextPageParam 函数的对象,如上所示。
这个函数检索下一页的页码。如果我们已经到了最后一页,我们可以返回undefined ,这样React Query就不会试图获取更多的数据。
最后,我们可以解构出由页面、isLoading 布尔值、isError 布尔值、hasNext 布尔值和fetchNextPage 函数组成的data 数组,以渲染相应的用户界面。
导入InfiniteScroll 组件
现在,从react-infinite-scroller ,导入InfiniteScroll 组件。在data.pages 数组的每个页面中映射出所有的帖子,以渲染<PostCard /> 组件中的<InfiniteScroll> 。
<InfiniteScroll hasMore={hasNextPage} loadMore={fetchNextPage}>
{data.pages.map((page) =>
page.results.map((post) => <PostCard key={post.id} post={post} />)
)}
</InfiniteScroll>;
<InfiniteScroll> 组件需要两个道具:hasMore ,一个布尔值,用于检查是否有更多的页面需要获取,以及loadMore 函数,用于在用户接近页面的末端时获取更多的帖子。
hasNextPage 从useInfiniteQuery() 的返回属性中解构出来的布尔值可以作为hasMore 道具的值。
同样地,返回属性也包含一个fetchNextPage 的函数,可以获取下一页的结果,并作为loadMore 的道具值。
最后,在把所有的代码片段和一些条件渲染拼凑在一起后,我们的App.js 文件将看起来像这样。
// App.js
import InfiniteScroll from "react-infinite-scroller";
import { useInfiniteQuery } from "react-query";
import Navbar from "./components/Navbar";
import PostCard from "./components/PostCard";
import "./styles.css";
export default function App() {
const fetchPosts = async ({ pageParam = 1 }) => {
const response = await fetch(
`https://picsum.photos/v2/list?page=${pageParam}&limit=10`
);
const results = await response.json();
return { results, nextPage: pageParam + 1, totalPages: 100 };
};
const {
data,
isLoading,
isError,
hasNextPage,
fetchNextPage
} = useInfiniteQuery("posts", fetchPosts, {
getNextPageParam: (lastPage, pages) => {
if (lastPage.nextPage < lastPage.totalPages) return lastPage.nextPage;
return undefined;
}
});
return (
<div className="App">
<Navbar />
<main>
{isLoading ? (
<p>Loading...</p>
) : isError ? (
<p>There was an error</p>
) : (
<InfiniteScroll hasMore={hasNextPage} loadMore={fetchNextPage}>
{data.pages.map((page) =>
page.results.map((post) => <PostCard key={post.id} post={post} />)
)}
</InfiniteScroll>
)}
</main>
</div>
);
}
这样,最终呈现出类似Instagram的无限滚动feed。
结论
至此,你已经成功地使用React Query、Lorem Picsum API和ReactInfiniteScroll 组件建立了自己的图片无限滚动feed。你可以用这个概念来为你的项目建立任何类型的无限滚动feed。
The postBuild an Instagram-like infinite scrolling feed with React Queryappeared first onLogRocket Blog.