分页
¥Pagination
请更新至最新版本(≥0.3.0)才能使用此 API。以前的 useSWRPages
API 现已弃用。
¥Please update to the latest version (≥ 0.3.0) to use this API. The previous useSWRPages
API is now deprecated.
SWR 提供了专用的 API useSWRInfinite
来支持常见的 UI 模式,例如分页和无限加载。
¥SWR provides a dedicated API useSWRInfinite
to support common UI patterns such as pagination and infinite loading.
何时使用 useSWR
¥When to Use useSWR
分页
¥Pagination
首先,如果我们构建如下内容,我们可能不需要 useSWRInfinite
,但可以只使用 useSWR
:
¥First of all, we might NOT need useSWRInfinite
but can use just useSWR
if we are building something like this:
...这是一个典型的分页 UI。让我们看看如何使用 useSWR
轻松实现:
¥...which is a typical pagination UI. Let's see how it can be easily implemented with
useSWR
:
function App () {
const [pageIndex, setPageIndex] = useState(0);
// The API URL includes the page index, which is a React state.
const { data } = useSWR(`/api/data?page=${pageIndex}`, fetcher);
// ... handle loading and error states
return <div>
{data.map(item => <div key={item.id}>{item.name}</div>)}
<button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
<button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
</div>
}
此外,我们可以为这个 "页面组件" 创建一个抽象:
¥Furthermore, we can create an abstraction for this "page component":
function Page ({ index }) {
const { data } = useSWR(`/api/data?page=${index}`, fetcher);
// ... handle loading and error states
return data.map(item => <div key={item.id}>{item.name}</div>)
}
function App () {
const [pageIndex, setPageIndex] = useState(0);
return <div>
<Page index={pageIndex}/>
<button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
<button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
</div>
}
由于 SWR 的缓存,我们可以预加载下一页。我们在隐藏的 div 中渲染下一页,因此 SWR 将触发下一页的数据请求。当用户导航到下一页时,数据已经存在:
¥Because of SWR's cache, we get the benefit to preload the next page. We render the next page inside a hidden div, so SWR will trigger the data fetching of the next page. When the user navigates to the next page, the data is already there:
function App () {
const [pageIndex, setPageIndex] = useState(0);
return <div>
<Page index={pageIndex}/>
<div style={{ display: 'none' }}><Page index={pageIndex + 1}/></div>
<button onClick={() => setPageIndex(pageIndex - 1)}>Previous</button>
<button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
</div>
}
只需 1 行代码,我们就可以获得更好的用户体验。useSWR
钩子太强大了,大部分场景都被它覆盖了。
¥With just 1 line of code, we get a much better UX. The useSWR
hook is so powerful,
that most scenarios are covered by it.
无限加载
¥Infinite Loading
有时我们想要构建一个无限加载的 UI,使用 "加载更多" 按钮将数据附加到列表(或在滚动时自动补齐):
¥Sometimes we want to build an infinite loading UI, with a "Load More" button that appends data to the list (or done automatically when you scroll):
为了实现这一点,我们需要在此页面上发出动态数量的请求。React Hooks 有 一些规则 (opens in a new tab),所以我们不能做这样的事情:
¥To implement this, we need to make dynamic number of requests on this page. React Hooks have a couple of rules (opens in a new tab), so we CANNOT do something like this:
function App () {
const [cnt, setCnt] = useState(1)
const list = []
for (let i = 0; i < cnt; i++) {
// 🚨 This is wrong! Commonly, you can't use hooks inside a loop.
const { data } = useSWR(`/api/data?page=${i}`)
list.push(data)
}
return <div>
{list.map((data, i) =>
<div key={i}>{
data.map(item => <div key={item.id}>{item.name}</div>)
}</div>)}
<button onClick={() => setCnt(cnt + 1)}>Load More</button>
</div>
}
相反,我们可以使用我们创建的 <Page />
抽象来实现它:
¥Instead, we can use the <Page />
abstraction that we created to achieve it:
function App () {
const [cnt, setCnt] = useState(1)
const pages = []
for (let i = 0; i < cnt; i++) {
pages.push(<Page index={i} key={i} />)
}
return <div>
{pages}
<button onClick={() => setCnt(cnt + 1)}>Load More</button>
</div>
}
高级案例
¥Advanced Cases
但是,在某些高级用例中,上述解决方案不起作用。
¥However, in some advanced use cases, the solution above doesn't work.
例如,我们仍然实现相同的 "加载更多" UI,但还需要显示有关总共有多少项的数字。我们不能再使用 <Page />
解决方案,因为顶层 UI (<App />
) 需要每个页面内的数据:
¥For example, we are still implementing the same "Load More" UI, but also need to display a number
about how many items are there in total. We can't use the <Page />
solution anymore because
the top level UI (<App />
) needs the data inside each page:
function App () {
const [cnt, setCnt] = useState(1)
const pages = []
for (let i = 0; i < cnt; i++) {
pages.push(<Page index={i} key={i} />)
}
return <div>
<p>??? items</p>
{pages}
<button onClick={() => setCnt(cnt + 1)}>Load More</button>
</div>
}
另外,如果分页 API 是基于游标的,则该解决方案也不起作用。由于每个页面都需要前一页的数据,因此它们不是孤立的。
¥Also, if the pagination API is cursor based, that solution doesn't work either. Because each page needs the data from the previous page, they're not isolated.
这就是这款新型 useSWRInfinite
Hook 的作用。
¥That's how this new useSWRInfinite
Hook can help.
useSWRInfinite
useSWRInfinite
使我们能够用一个 Hook 触发多个请求。它看起来是这样的:
¥useSWRInfinite
gives us the ability to trigger a number of requests with one Hook. This is how it looks:
import useSWRInfinite from 'swr/infinite'
// ...
const { data, error, isLoading, isValidating, mutate, size, setSize } = useSWRInfinite(
getKey, fetcher?, options?
)
与 useSWR
类似,这个新 Hook 接受一个返回请求键的函数、一个 fetcher 函数和选项。它返回 useSWR
返回的所有值,包括 2 个额外值:页面大小和页面大小设置器,如 React 状态。
¥Similar to useSWR
, this new Hook accepts a function that returns the request key, a fetcher function, and options.
It returns all the values that useSWR
returns, including 2 extra values: the page size and a page size setter, like a React state.
在无限加载中,一页就是一个请求,我们的目标是获取多个页面并渲染它们。
¥In infinite loading, one page is one request, and our goal is to fetch multiple pages and render them.
如果你使用的是 SWR 0.x 版本,则需要从 swr
:
import { useSWRInfinite } from 'swr'
导入 useSWRInfinite
¥If you are using SWR 0.x versions, useSWRInfinite
needs to be imported from swr
:
import { useSWRInfinite } from 'swr'
API
参数
¥Parameters
-
getKey
:接受索引和上一页数据的函数,返回页面的键¥
getKey
: a function that accepts the index and the previous page data, returns the key of a page -
fetcher
:与useSWR
的 获取函数 相同¥
fetcher
: same asuseSWR
's fetcher function -
options
:接受useSWR
支持的所有选项,还有 4 个额外选项:¥
options
: accepts all the options thatuseSWR
supports, with 4 extra options:-
initialSize = 1
:最初应加载的页数¥
initialSize = 1
: number of pages should be loaded initially -
revalidateAll = false
:总是尝试重新验证所有页面¥
revalidateAll = false
: always try to revalidate all pages -
revalidateFirstPage = true
:总是尝试重新验证第一页¥
revalidateFirstPage = true
: always try to revalidate the first page -
persistSize = false
:当第一页的键更改时,不要将页面大小重置为 1(或initialSize
,如果设置)¥
persistSize = false
: don't reset the page size to 1 (orinitialSize
if set) when the first page's key changes -
parallel = false
:并行获取多个页面¥
parallel = false
: fetches multiple pages in parallel
-
请注意,initialSize
选项在生命周期中不允许更改。
¥Note that the initialSize
option is not allowed to change in the lifecycle.
返回值
¥Return Values
-
data
:每个页面的获取响应值的数组¥
data
: an array of fetch response values of each page -
error
:与useSWR
的error
相同¥
error
: same asuseSWR
'serror
-
isLoading
:与useSWR
的isLoading
相同¥
isLoading
: same asuseSWR
'sisLoading
-
isValidating
:与useSWR
的isValidating
相同¥
isValidating
: same asuseSWR
'sisValidating
-
mutate
:与useSWR
的绑定 mutate 函数相同,但操作数据数组¥
mutate
: same asuseSWR
's bound mutate function but manipulates the data array -
size
:将获取和返回的页面数¥
size
: the number of pages that will be fetched and returned -
setSize
:设置需要抓取的页数¥
setSize
: set the number of pages that need to be fetched
示例 1:基于索引的分页 API
¥Example 1: Index Based Paginated API
对于普通的基于索引的 API:
¥For normal index based APIs:
GET /users?page=0&limit=10
[
{ name: 'Alice', ... },
{ name: 'Bob', ... },
{ name: 'Cathy', ... },
...
]
// A function to get the SWR key of each page,
// its return value will be accepted by `fetcher`.
// If `null` is returned, the request of that page won't start.
const getKey = (pageIndex, previousPageData) => {
if (previousPageData && !previousPageData.length) return null // reached the end
return `/users?page=${pageIndex}&limit=10` // SWR key
}
function App () {
const { data, size, setSize } = useSWRInfinite(getKey, fetcher)
if (!data) return 'loading'
// We can now calculate the number of all users
let totalUsers = 0
for (let i = 0; i < data.length; i++) {
totalUsers += data[i].length
}
return <div>
<p>{totalUsers} users listed</p>
{data.map((users, index) => {
// `data` is an array of each page's API response.
return users.map(user => <div key={user.id}>{user.name}</div>)
})}
<button onClick={() => setSize(size + 1)}>Load More</button>
</div>
}
getKey
功能是 useSWRInfinite
和 useSWR
之间的主要区别。它接受当前页面的索引以及上一页的数据。因此基于索引和基于游标的分页 API 都可以得到很好的支持。
¥The getKey
function is the major difference between useSWRInfinite
and useSWR
.
It accepts the index of the current page, as well as the data from the previous page.
So both index based and cursor based pagination API can be supported nicely.
此外,data
不再只是一个 API 响应。它是多个 API 响应的数组:
¥Also data
is no longer just one API response. It's an array of multiple API responses:
// `data` will look like this
[
[
{ name: 'Alice', ... },
{ name: 'Bob', ... },
{ name: 'Cathy', ... },
...
],
[
{ name: 'John', ... },
{ name: 'Paul', ... },
{ name: 'George', ... },
...
],
...
]
示例 2:基于光标或偏移量的分页 API
¥Example 2: Cursor or Offset Based Paginated API
假设 API 现在需要一个游标并返回下一个游标以及数据:
¥Let's say the API now requires a cursor and returns the next cursor alongside with the data:
GET /users?cursor=123&limit=10
{
data: [
{ name: 'Alice' },
{ name: 'Bob' },
{ name: 'Cathy' },
...
],
nextCursor: 456
}
我们可以将 getKey
函数更改为:
¥We can change our getKey
function to:
const getKey = (pageIndex, previousPageData) => {
// reached the end
if (previousPageData && !previousPageData.data) return null
// first page, we don't have `previousPageData`
if (pageIndex === 0) return `/users?limit=10`
// add the cursor to the API endpoint
return `/users?cursor=${previousPageData.nextCursor}&limit=10`
}
并行抓取模式
¥Parallel Fetching Mode
请更新至最新版本(≥2.1.0)才能使用此 API。
¥Please update to the latest version (≥ 2.1.0) to use this API.
useSWRInfinite 的默认行为是按顺序获取每个页面的数据,因为键创建基于之前获取的数据。然而,按顺序获取大量页面的数据可能不是最佳的,特别是在页面不相互依赖的情况下。通过为 true
指定 parallel
选项,你可以并行独立地获取页面,这可以显着加快加载过程。
¥The default behavior of useSWRInfinite is to fetch data for each page in sequence, as key creation is based on the previously fetched data. However, fetching data sequentially for a large number of pages may not be optimal, particularly if the pages are not interdependent. By specifying parallel
option to true
will let you fetch pages independently in parallel, which can significantly speed up the loading process.
// parallel = false (default)
// page1 ===> page2 ===> page3 ===> done
//
// parallel = true
// page1 ==> done
// page2 =====> done
// page3 ===> done
//
// previousPageData is always `null`
const getKey = (pageIndex, previousPageData) => {
return `/users?page=${pageIndex}&limit=10`
}
function App () {
const { data } = useSWRInfinite(getKey, fetcher, { parallel: true })
}
当启用 parallel
选项时,getKey
函数的 previousPageData
参数将变为 null
。
¥The previousPageData
argument of the getKey
function becomes null
when you enable the parallel
option.
重新验证特定页面
¥Revalidate Specific Pages
请更新到最新版本 (≥ 2.2.5) 以使用此 API。
¥Please update to the latest version (≥ 2.2.5) to use this API.
useSWRInfinite
突变的默认行为是重新验证已加载的所有页面。但是你可能只想重新验证已更改的特定页面。你可以通过将函数传递给 revalidate
选项来仅重新验证特定页面。
¥The default behavior of the mutation of useSWRInfinite
is to revalidate all pages that have been loaded. But you might want to revalidate only the specific pages that have been changed. You can revalidate only specific pages by passing a function to the revalidate
option.
每个页面都会调用 revalidate
函数。
¥The revalidate
function is called for each page.
function App() {
const { data, mutate, size } = useSWRInfinite(
(index) => [`/api/?page=${index + 1}`, index + 1],
fetcher
);
mutate(data, {
// only revalidate the last page
revalidate: (pageData, [url, page]) => page === size
});
}
使用 useSWRInfinite
进行全局修改
¥Global Mutate with useSWRInfinite
useSWRInfinite
使用特殊的缓存键将所有页面数据与每个页面数据一起存储到缓存中,因此你必须在 swr/infinite
中使用 unstable_serialize
通过全局修改重新验证数据。
¥useSWRInfinite
stores all page data into the cache with a special cache key along with each page data, so you have to use unstable_serialize
in swr/infinite
to revalidate the data with the global mutate.
import { useSWRConfig } from "swr"
import { unstable_serialize } from "swr/infinite"
function App() {
const { mutate } = useSWRConfig()
mutate(unstable_serialize(getKey))
}
顾名思义,unstable_serialize
不是一个稳定的 API,因此我们将来可能会更改它。
¥As the name implies, unstable_serialize
is not a stable API, so we might change it in the future.
高级特性
¥Advanced Features
这是一个例子 展示了如何使用 useSWRInfinite
实现以下功能:
¥Here is an example showing how you can implement the following features with useSWRInfinite
:
-
加载状态
¥loading states
-
如果它是空的,则显示一个特殊的 UI
¥show a special UI if it's empty
-
如果到达终点则禁用 "加载更多" 按钮
¥disable the "Load More" button if reached the end
-
可变数据源
¥changeable data source
-
刷新整个列表
¥refresh the entire list