使用React查询的懒惰加载的指南

290 阅读4分钟

这是使用React Query与TypeScript的系列文章中的另一篇文章。

以前的文章:

这篇文章将介绍如何使用React Query来渲染一个被懒散加载的列表。当用户滚动到列表底部时,更多的数据将被请求并呈现出来。

Lazy loading 查询客户端提供者

React Query需要在使用它的组件上面有一个QueryClientProvider

import { QueryClient, QueryClientProvider } from "react-query";
...

const queryClient = new QueryClient();
render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>,
  rootElement
);

我们在组件树的顶部添加了QueryClientProvider ,使React Query对任何组件都可用。

抓取功能

我们的获取函数如下:

type CharactersPage = {
  results: Character[];
  next: number | undefined;
};
type Character = {
  name: string;
};

async function getData({ pageParam = 1 }) {
  const response = await fetch(
    `https://swapi.dev/api/people/?page=${pageParam}`
  );
  if (!response.ok) {
    throw new Error("Problem fetching data");
  }
  const dataFromServer = await response.json();
  assertIsCharacterResponse(dataFromServer);
  const data: CharactersPage = {
    results: dataFromServer.results,
    next: dataFromServer.next === null ? undefined : pageParam + 1,
  };
  return data;
}

该函数接收一个包含pageParam 属性的对象参数,该属性是被请求的页码。pageParam 将在第一次请求中作为undefined 传入,所以默认为1

该函数发出一个简单的请求,如果不成功就会引发一个错误。

响应的数据类型通过assertIsCharacterResponse 类型断言函数断定为CharacterResponse ,我们稍后会看一下。

响应数据被映射到一个稍微不同的对象中。next 属性是下一页的页码,如果没有下一页,则是undefined

这里是assertIsCharacterResponse 类型的断言函数:

type CharacterResponse = {
  results: Character[];
  next: string;
};
function assertIsCharacterResponse(
  response: any
): asserts response is CharacterResponse {
  if (!("results" in response && "next" in response)) {
    throw new Error("Not results");
  }
  if (response.results.length > 0) {
    const firstResult = response.results[0];
    if (!("name" in firstResult)) {
      throw new Error("Not characters");
    }
  }
}

查询

之前的文章中,我们使用了useQuery 钩子来调用获取函数并返回有用的状态变量。还有一个钩子叫做 useInfiniteQuery的钩子,它在渲染懒散加载的页面中的列表时非常有用。

我们可以使用useInfiniteQuery ,如下所示:

import { useInfiniteQuery } from "react-query";

export function App() {
  const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
    status,
  } = useInfiniteQuery<CharactersPage, Error>("characters", getData, {
    getNextPageParam: (lastPage) => lastPage.next,
  });
}

useInfiniteQuery 是非常类似于 。我们已经传入了以下参数useQuery

  • 一个键,"characters" ,用于查询。
  • 获取的函数,getData
  • 一个返回下一个页面的函数。调用这个函数是为了把下一页的页码传给取回函数。该函数接收当前的响应数据并返回下一页的页码。

我们已经对以下状态变量进行了结构化处理。

  • data:这包含了以下结构中的所有页数的数据:
{ 
  pages: [
    [arrayOfItemsInPage1],
    [arrayOfItemsInPage2], 
    ...
  ], 
  pageParams: [page1Number, page2Number, ...]
}
  • error:如果出现了错误,则是错误对象。
  • fetchNextPage:调用函数以获得下一页的数据。
  • hasNextPage:是否有下一页。
  • isFetchingNextPage:是否当前正在请求下一页。
  • status:获取过程的当前状态。

渲染列表

列表的渲染方式如下:

export function App() {
  ...
  if (status === "loading") {
    return <div>...</div>;
  }
  if (status === "error") {
    return <div>{error!.message}</div>;
  }
  return (
    <div>
      <div>
        {data &&
          data.pages.map((group, i) => (
            <React.Fragment key={i}>
              {group.results.map((character) => (
                <div key={character.name}>{character.name}</div>
              ))}
            </React.Fragment>
          ))}
      </div>
      <div>
        {hasNextPage && (
          <button
            onClick={() => fetchNextPage()}
            disabled={!hasNextPage || isFetchingNextPage}
          >
            {isFetchingNextPage ? "Loading ..." : "More"}
          </button>
        )}
      </div>
    </div>
  );
}

status 状态用于显示加载指标,如果存在错误,则显示错误。

如果有数据,所有的数据页都会被渲染出来。记住,数据的结构是数组的数组,根数组是页面,子数组是每个页面中的数据。所以,我们必须对这些数组进行映射。

我们还渲染了一个**"更多**"按钮,当点击该按钮时,需要下一页的数据。

下面是它的样子:

Button load

滚动到列表底部时的加载情况

这并不坏,但我们可以通过在用户滚动到列表底部时自动加载下一页来改善用户体验。因此,用户将不再需要点击多按钮来获得更多的数据--它将自动出现。

我们可以利用一个叫做react-infinite-scroll-component 的软件包来帮助我们。我们可以通过在终端运行以下命令来安装它:

npm install react-infinite-scroll-component

我们使用它的方法如下:

import InfiniteScroll from "react-infinite-scroll-component";
...
export function App() {
  ...
  if (data === undefined) {
    return null;
  }
  const dataLength = data.pages.reduce((counter, page) => {
    return counter + page.results.length;
  }, 0);
  return (
    <InfiniteScroll
      dataLength={dataLength}
      next={fetchNextPage}
      hasMore={!!hasNextPage}
      loader={<div>Loading...</div>}
    >
      {data.pages.map((group, i) => (
        <React.Fragment key={i}>
          {group.results.map((character) => (
            <p key={character.name}>{character.name}</p>
          ))}
        </React.Fragment>
      ))}
    </InfiniteScroll>
  );
}

我们使用react-infinite-scroll-component 包中的InfiniteScroll 组件,并向其传递以下内容:

  • dataLength:这是在所有页面的列表中的项目数量。我们使用一个减速器函数来计算。
  • next:这是取下一页数据的函数。
  • hasMore:是否有更多的页面。!! ,在hasNextPage 之前使用,将其转换为boolean ,而不使用undefined
  • loader:这是一个渲染道具,用于在获取页面时渲染一个加载指标。

下面是结果:

Scroll load

很好😊

这篇文章中的代码可以在CodeSandbox中找到:codesandbox.io/s/react-que…