#2 React 查询:无限滚动
大家好!你们好吗?还好吗?最近怎么样?
正如我在第一篇关于 React Query 的文章中所承诺的那样,我讨论了这个状态管理工具,它提供了许多功能,如分页、缓存、自动重新取回、预取等等。
在本文中,我将讨论 React 查询的惊人功能——无限滚动。
介绍
你可能已经在所有社交媒体上见过这个功能了,比如 X(不过我更喜欢 Twitter),或者 Facebook、LinkedIn……这些社交媒体没有分页功能,但可以无限滚动,数据会自动生成。你无需使用按钮(下一页或上一页)即可使用分页功能。
但下面的无限滚动是一种分页。
因此,考虑到这一点,让我们看看代码!!!
亲自动手
我将使用上一篇文章中创建的同一个项目。因此,我不会演示如何安装或配置 React 查询。如果您不了解,建议您阅读第一篇文章,其中我讲解并演示了如何安装、设置等。
您可以在这里找到:https://dev.to/kevin-uehara/1-react-query-introducing-pagination-caching-and-pre-fetching-21p8
考虑到这一点,我将预先假设您已经配置了项目或者您了解反应查询的基础知识。
我将使用相同的 Fake API 来提供数据。JSON 占位符,但这次我将使用Todo
端点。
例如访问:https://jsonplaceholder.typicode.com/todos? _pages=0&_limit=10
因此,在之前的同一个项目中,正如我所说,让我们为我们的组件 Todos 创建文件夹:src/Todo/index.tsx
。
在其他情况下,我可能会types.ts
为我们的类型创建。但我们只会在这个文件中使用。所以我会在组件中创建类型。此外,我还会添加MAX_POST_PAGE
常量。
src/Todo/index.tsx
const MAX_POST_PAGE = 10;
interface TodoType {
id: number;
title: string;
}
因此我们的 Todo 类型限制为 10,它将仅使用 id 和 title。
现在让我们创建函数来获取数据:
const fetchTodos = async ({ pageParam }: { pageParam: number }) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos?_pages=${pageParam}&_limit=${MAX_POST_PAGE}`
);
const todos = (await response.json()) as TodoType[];
return todos;
};
注意,该函数将接收代表页码的 pageParam。我将以对象的形式接收并使用 destruct。
到目前为止我们的组件看起来是这样的:
const MAX_POST_PAGE = 10;
interface TodoType {
id: number;
title: string;
}
const fetchTodos = async ({ pageParam }: { pageParam: number }) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos?_pages=${pageParam}&_limit=${MAX_POST_PAGE}`
);
const todos = (await response.json()) as TodoType[];
return todos;
};
export const Todo = () => {
return <></>;
现在让我们创建 Todo 组件的内容。
让我们创建一个观察者引用,使用useRef
钩子并将 IntersectionObserver 类型作为泛型传递,如下所示:
const observer = useRef<IntersectionObserver>();
Observer
是desing pattern
,如定义:
Observer is a software design pattern that defines a one-to-many dependency between objects so that when an object changes state, all of its dependents are notified and updated automatically.
观察者,顾名思义,它会观察某个对象的状态。如果依赖项发生更新,正在监听(观察)它的对象就会收到通知。
但你可能会想🤔我为什么要解释所有这些概念。好吧,我们需要使用观察者来判断用户是否在页面的末尾,以便通过下一页参数获取新数据。
是的!正如我之前所说,无限滚动是一种不同类型的分页🤯
让我们使用 React 查询的钩子useInfiniteQuery
。它非常类似于useQuery
:
const { data, error, fetchNextPage, hasNextPage, isFetching, isLoading } =
useInfiniteQuery({
queryKey: ["todos"],
queryFn: ({ pageParam }) => fetchTodos({ pageParam }),
getNextPageParam: (lastPage, allPages) => {
return lastPage.length ? allPages.length + 1 : undefined;
},
});
我们将析构并获取数据、错误消息、fetchNextpage 函数、hasNextPage 属性、isFectching 和 isLoading 状态。
我们将传递键“todos” queryKey
、函数 fetchTodosqueryFn
并创建一个函数getNextPageParam
来获取下一页,如果有数据则递增并验证。
现在让我们创建一个函数来观察用户是否到达页面末尾。
const lastElementRef = useCallback(
(node: HTMLDivElement) => {
if (isLoading) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasNextPage && !isFetching) {
fetchNextPage();
}
});
if (node) observer.current.observe(node);
},
[fetchNextPage, hasNextPage, isFetching, isLoading]
);
如果你现在还不明白这个功能,别担心。请冷静地阅读。
我们将接收节点和一些div
要观察的元素。
首先,我验证状态是否为“正在加载”,如果是,我简单地返回任何内容并退出函数。
现在我验证是否已经有了 IntersectionObserver 的实例。如果已经有了,我就断开连接,因为我不想创建多个观察者实例。
现在,如果我们没有。让我们通过new IntersectionObserver()
传递entries
箭头函数的参数来实例化它。现在我们将验证页面是否相交、是否有下一页以及是否正在获取。
如果所有这些条件都通过了验证,我将调用函数fetchNextPage()
返回的结果useInfiniteQuery
。
现在让我们传递观察引用node
。
就是这样!它不是个小怪兽吗?不过,如果我们冷静地读下去,就会发现其实也没那么复杂。
现在我将使用 reduce 来格式化我们的数据以简化我们的数据:
const todos = useMemo(() => {
return data?.pages.reduce((acc, page) => {
return [...acc, ...page];
}, []);
}, [data]);
现在让我们验证并返回可能的状态并返回值:
if (isLoading) return <h1>Loading...</h1>;
if (error) return <h1>Error on fetch data...</h1>;
return (
<div>
{todos &&
todos.map((item) => (
<div key={item.id} ref={lastElementRef}>
<p>{item.title}</p>
</div>
))}
{isFetching && <div>Fetching more data...</div>}
</div>
在简历中我们将有这个部分:
src/Todos/index.tsx
import { useCallback, useMemo, useRef } from "react";
import { useInfiniteQuery } from "react-query";
const MAX_POST_PAGE = 10;
interface TodoType {
id: number;
title: string;
}
const fetchTodos = async ({ pageParam }: { pageParam: number }) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos?_pages=${pageParam}&_limit=${MAX_POST_PAGE}`
);
const todos = (await response.json()) as TodoType[];
return todos;
};
export const Todo = () => {
const observer = useRef<IntersectionObserver>();
const { data, error, fetchNextPage, hasNextPage, isFetching, isLoading } =
useInfiniteQuery({
queryKey: ["todos"],
queryFn: ({ pageParam }) => fetchTodos({ pageParam }),
getNextPageParam: (lastPage, allPages) => {
return lastPage.length ? allPages.length + 1 : undefined;
},
});
const lastElementRef = useCallback(
(node: HTMLDivElement) => {
if (isLoading) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasNextPage && !isFetching) {
fetchNextPage();
}
});
if (node) observer.current.observe(node);
},
[fetchNextPage, hasNextPage, isFetching, isLoading]
);
const todos = useMemo(() => {
return data?.pages.reduce((acc, page) => {
return [...acc, ...page];
}, []);
}, [data]);
if (isLoading) return <h1>Carregando mais dados...</h1>;
if (error) return <h1>Erro ao carregar os dados</h1>;
return (
<div>
{todos &&
todos.map((item) => (
<div key={item.id} ref={lastElementRef}>
<p>{item.title}</p>
</div>
))}
{isFetching && <div>Carregando mais dados...</div>}
</div>
);
};
现在main.tsx
我将替换App.tsx
我们之前的示例来呈现我们的 Todo 组件:
src/main.tsx
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<Todo />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</React.StrictMode>
现在将得到结果:
现在我们有了无限卷轴!!! 多么神奇啊,不是吗?
好了,就这样吧!!!
希望你们喜欢这篇关于这个神奇工具 React Query 的第二篇文章。
祝你一切安好。
非常感谢你读到这里。
联系方式:
Youtube:https://www.youtube.com/@ueharakevin/
Linkedin:https://www.linkedin.com/in/kevin-uehara/
Instagram:https://www.instagram.com/uehara_kevin/
Twitter:https: //twitter.com/ueharaDev
Github:https: //github.com/kevinuehara