react-query手把手教程14-分页

1,195 阅读4分钟

该系列其他文章可以点击查看专栏👉🏻👉🏻react-query手把手教程系列

场景

假如在百度,输入react-query进行搜索,你会得到两亿多条结果

image.png 但是这两亿条结果并不是一次性返回给你的,通常后端会进行分页操作,所以你需要不停地翻页才能获取到后面的信息

image.png

接下来将会为大家介绍如何在react-query中,进行分页操作。

实践

还是以github为例,将演示如何将issue列表进行分页。首先来看一下api的结构:

const url = `https://api.github.com/repos/${org}/${repo}/issues?page=${page}&per_page=${perPage}`;

上面的URL中github允许传入当前在第page页,每页有perPage条内容。

下面是实现请求的伪代码:

function fetchIssues({queryKey}) {
  const [issues, org, repo, {page, perPage}] = queryKey;
  const url = `https://api.github.com/repos/${org}/${repo}/issues?page=${page}&per_page=${perPage}`;
  return fetch(url).then(res => res.json());
}

const Issues = ({org, repo}) => {
  const [page, setPage] = useState(1);
  const perPage = 10;
  const issuesQuery = useQuery(
    ["issues", org, repo, {page, perPage}],
    fetchIssues
  );


  // ...
}

上面的例子中,创建了一个获取issue列表的方法fetchIssues,并且将page管理在state中,而页数由于目前是固定的,所以存储在了perPage的常量中。

你还需要一个翻页组件,来改变当前所在的页面,代码如下:

const Issues = ({org, repo}) => {
  const [page, setPage] = useState(1);
  // ...
  return (
    <div>
      { /* ... */ }
      <div>
        <button 
          onClick={() => setPage(page => page - 1)}
          disabled={page === 1}
        >
          上一页
        </button>
        <p>
          Page {page} {issuesQuery.isFetching ? "..." : ""}
        </p>
        <button
          onClick={() => setPage(page => page + 1)}
          disabled={
            !issuesQuery.data || issuesQuery.data.length === 0
          }
        >
          下一页
        </button>
      </div>
    </div>
  );
}

上面的组件在第一页时禁用了前一页按钮,在最后一页禁用了下一页,在翻页获取数据时,展示...表示正在获取数据中。

最后还需要一个组件来展示整体数据的加载状态、错误数据以及issue列表的展示

const Issues = ({org, repo}) => {
  // ...
  
  return (
    <div>
      <div>
        {issuesQuery.loading && <div>Loading...</div>}
        {issuesQuery.error && <div>Error!</div>}
        {issuesQuery.data && issuesQuery.data.map(issue => (
          <Issue key={issue.id} issue={issue} />
        ))}
      </div>
      {/* ... */}
    </div>
  )
}

完整的组件伪代码如下:

const Issues = ({org, repo}) => {
  const [page, setPage] = useState(1);
  const perPage = 10;
  const issuesQuery = useQuery(
    ["issues", org, repo, {page, perPage}],
    fetchIssues
  );


  return (
    <div>
      <div>
        {issuesQuery.loading && <div>Loading...</div>}
        {issuesQuery.error && <div>Error!</div>}
        {issuesQuery.data && issuesQuery.data.map(issue => (
          <Issue key={issue.id} issue={issue} />
        ))}
      </div>
      <div>
        <button
          onClick={() => setPage(page => page - 1)}
          disabled={page === 1}
        >
          Previous
        </button>
        <p>
          Page {page} {issuesQuery.isFetching ? "..." : ""}
        </p>
        <button
          onClick={() => setPage(page => page + 1)}
          disabled={
            !issuesQuery.data || issuesQuery.data.length === 0
          }
        >
          Next
        </button>
      </div>
    </div>
  );
}

但是在使用的过程中会发现:切换页面时,当前数据的会消失,并被替换为加载状态。

但是这样的体验并不是很好,可以在用户加载下一个页面时保留当前数据,这样数据的展示就不会中断。

此时就需要使用react-query中的keepPreviousData功能。

keepPreviousData

通过将keepPreviousData: true配置项传入useQuery钩子,每当查询键发生改变时,查询将会继续提供最后一次的查询数据,直到新的查询数据可用为止。在获取数据后react-query将会无缝切换至新的数据。

其实这个特性不仅可以用于分页,对于排序等场景也非常好用。对于之前例子的代码,可以改造成如下的样子:

const Issues = ({org, repo}) => {
  // ...
  const issuesQuery = useQuery(
    ["issues", org, repo, {page, perPage}],
    fetchIssues,
    {keepPreviousData: true}
  );
  
  return (
    // ...
  )
}

但是这会引发一个问题,如果后端返回数据较慢,用户在数据仍然在请求的情况下,又点击了下一页,此时就会出现问题。因此需要在这种情况下禁用翻页按钮。

但是你并不能使用isFetching属性!!!这样会导致在一般情况下的刷新,也被禁用翻页(比如:从别的标签页切换回当前页面时,查询的重新加载)。

react-query针对这种情况提供了isPreviousData属性来满足需求,因此改造后的代码如下:

const Issues = ({org, repo}) => {
  // ...
  return (
    <div>
      {/* ... */}
      <div>
        <button
          onClick={() => setPage(page => page - 1)}
          disabled={page === 1}
        >
          上一页
        </button>
        <p>
          Page {page} {issuesQuery.isFetching ? "..." : ""}
        </p>
        <button
          onClick={() => setPage(page => page + 1)}
          disabled={
            !issuesQuery.data || 
            issuesQuery.data.length === 0 || 
            issuesQuery.isPreviousData
          }
        >
          下一页
        </button>
      </div>
    </div>
  );
}

预加载分页数据

虽然上面的体验已经非常极致了,但是我们可以做到更好的体验。

之前的章节中,讲述了当用户的鼠标悬浮到上一页或者下一页的按钮上时就预先获取数据,让用户有更好的体验感。

但是在分页的使用场景中,用户有非常大的几率会点击下一页,因此可以通过queryClient.prefetchQuery预先获取该数据,代码如下:

const Issues = ({org, repo}) => {
  const queryClient = useQueryClient();
  const [page, setPage] = useState(1);
  const perPage = 10;
  const issuesQuery = useQuery(
    ["issues", org, repo, {page, perPage}],
    fetchIssues,
    {keepPreviousData: true}
  );

  // ①
  useEffect(() => {
    queryClient.prefetchQuery(
      ["issues", org, repo, {page: page + 1, perPage}],
      fetchIssues
    );
  }, [org, repo, page, perPage, queryClient]);


  // ...
}

上面的代码中,通过useEffect钩子监听每当页面有变化时,都会预先获取下一页的内容。

这样我们就做到了如德芙一般丝滑的翻页体验؏؏☝ᖗ乛◡乛ᖘ☝؏؏

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 22 天,点击查看活动详情