无限加载

无限加载是目前UI设计时比较常用的一个特性,在一些情况下可以直接替代传统的分页技术。传统的分页技术在使用SWR实现的时候已经非常简单,只需要确定URL即可。

例如以下专门用于获取分页页面的组件。

function Page({ page }) {
  const { data } = useSWR(`/api/data?page=${page}`, fetcher);

  return data.map(item => <div key={item.id}>{item.name}</div>);
}

对于普通的页面加载是可以这样来使用的,但是对于无限加载来说,通常都需要通过一个Hook来触发多个请求,这时就需要使用SWR提供的新的Hook了。useSWRInfinite Hook的使用格式如下。

const { data, error, isValidating, mutate, size, setSize } = useSWRInfinite(getKey, fetcher?, options?);

useSWRInfinite的使用格式可以看出来,它与useSWR的一个不同点在于,useSWRInfinite需要传入一个函数来返回key,而不是直接使用一个字符串,现在这个getKey函数也固定了所接受的参数,第一个是当前页面的索引,第二个是上一页的数据。而且useSWRInfinite返回的内容也发生了一些变化。主要的变化有以下这些。

  • data,虽然与useSWR返回数据的数据项名称一样,但是useSWRInfinite返回的是一个数组,其中每个元素都是一个请求的返回值。
  • size,即将请求和返回的页面数量。
  • setSize,设置需要请求的页面数量。

useSWRInfinite基本上可以使用useSWR所有的配置参数,但还多了以下几个配置项。

  • initialSize,设置最初应加载的页面数量,默认为1
  • revalidateAll,设置始终尝试重新验证所有页面,默认为false
  • persistSize,设置当第一页的key发生变化的时候,是否将page size或者initialSize重置为1,默认为false

以下是一个useSWRInfinite的使用示例。

const getKey = (pageIndex, previousPage) => {
  // 如果getKey函数返回null,那么请求就不会再开始
  if (previousPage && !previousPage.length) return null;
  // 如果回到了首页,那么就可以传送不带有page参数的key
  if (pageIndex == 0) return `/orders`;
  return `/orders?page=${pageIndex}`;
};

function Orders() {
  const { data, size, setSize } = useSWRInfinite(getKey, fetcher);
  if (!data) return <Loading />;

  // 计算已经获取的内容数量
  let orderCount = 0;
  data.forEach(item => (orderCount += item.length));

  return (
    <div>
      <p>{orderCount} Orders listed.</p>
      {data.map((orders, index) => {
        return orders.map(order => <Order detail={order} />);
      })}
      <button onClick={() => setSize(size + 1)}>Load More</button>
    </div>
  );
}

useSWRInfinite在使用的使用还有一些状态判断技巧,在需要的时候可以参考以下示例。

  • !data && !error,是否正在加载第一页数据。
  • (!data && !error) || (size > 0 && data && typeof data[size - 1] === "undefined"),是否正在加载更多的页面。
  • data?.[0]?.length === 0,数据服务端没有返回数据。
  • isValidating && data && data.length === size,是否正在刷新数据。