分頁
請更新至最新版本(≥ 0.3.0)以使用此 API。先前的 useSWRPages
API 現在已棄用。
SWR 提供專用的 API useSWRInfinite
來支援常見的 UI 模式,例如分頁和無限載入。
何時使用 useSWR
分頁
首先,如果我們正在建立如下的內容,我們可能不需要 useSWRInfinite
,而可以使用 useSWR
就好
...這是一個典型的分頁 UI。讓我們看看如何使用 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>
}
此外,我們可以為這個「頁面元件」建立一個抽象
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 將觸發下一頁的資料獲取。當使用者導航到下一頁時,資料已經在那裡
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 行程式碼,我們就可以獲得更好的 UX。useSWR
Hook 非常強大,大多數場景都涵蓋在其中。
無限載入
有時我們想要建立一個無限載入 UI,帶有一個「載入更多」按鈕,該按鈕將資料附加到清單中(或在您捲動時自動完成)
為了實現這一點,我們需要在這個頁面上發出動態數量的請求。React Hooks 有一些規則(在新分頁開啟),因此我們不能做這樣的事情
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 />
抽象來實現它
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>
}
進階案例
然而,在某些進階使用案例中,上述解決方案不起作用。
例如,我們仍然在實作相同的「載入更多」UI,但也需要顯示總共有多少項目的數字。我們不能再使用 <Page />
解決方案了,因為頂層 UI(<App />
)需要每個頁面內的資料
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 是基於游標的,該解決方案也無效。因為每個頁面都需要前一個頁面的資料,它們並非孤立的。
這就是這個新的 useSWRInfinite
Hook 可以提供幫助的原因。
useSWRInfinite
useSWRInfinite
讓我們能夠使用一個 Hook 觸發多個請求。它的外觀如下
import useSWRInfinite from 'swr/infinite'
// ...
const { data, error, isLoading, isValidating, mutate, size, setSize } = useSWRInfinite(
getKey, fetcher?, options?
)
與 useSWR
類似,這個新的 Hook 接受一個返回請求鍵的函式、一個 fetcher 函式和選項。它返回 useSWR
返回的所有值,包括 2 個額外的值:頁面大小和頁面大小設定器,就像 React 狀態一樣。
在無限載入中,一個頁面是一個請求,我們的目標是獲取多個頁面並渲染它們。
如果您正在使用 SWR 0.x 版本,則需要從 swr
匯入 useSWRInfinite
import { useSWRInfinite } from 'swr'
API
參數
getKey
:一個接受索引和前一頁資料的函式,返回頁面的鍵fetcher
:與useSWR
的fetcher 函式相同options
:接受useSWR
支援的所有選項,外加 4 個額外選項initialSize = 1
:初始載入的頁面數量revalidateAll = false
:總是嘗試重新驗證所有頁面revalidateFirstPage = true
:總是嘗試重新驗證第一頁persistSize = false
:當第一頁的鍵值變更時,不要將頁面大小重設為 1(或如果設定了initialSize
的值)parallel = false
:平行取得多個頁面
請注意,initialSize
選項不允許在生命週期中變更。
回傳值
data
:每個頁面的抓取回應值陣列error
:與useSWR
的error
相同isLoading
:與useSWR
的isLoading
相同isValidating
:與useSWR
的isValidating
相同mutate
:與useSWR
的綁定 mutate 函數相同,但會操作資料陣列size
:將會 抓取並回傳的頁面數量setSize
:設定需要抓取的頁面數量
範例 1:基於索引的分頁 API
對於一般的基於索引的 API
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 都能良好地支援。
此外,data
不再只是一個 API 回應。它是一個包含多個 API 回應的陣列
// `data` will look like this
[
[
{ name: 'Alice', ... },
{ name: 'Bob', ... },
{ name: 'Cathy', ... },
...
],
[
{ name: 'John', ... },
{ name: 'Paul', ... },
{ name: 'George', ... },
...
],
...
]
範例 2:基於游標或偏移量的分頁 API
假設現在 API 需要一個游標,並在資料旁邊回傳下一個游標
GET /users?cursor=123&limit=10
{
data: [
{ name: 'Alice' },
{ name: 'Bob' },
{ name: 'Cathy' },
...
],
nextCursor: 456
}
我們可以將 getKey
函數變更為
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`
}
平行抓取模式
請更新到最新版本(≥ 2.1.0)以使用此 API。
useSWRInfinite 的預設行為是依序抓取每個頁面的資料,因為金鑰的建立是基於先前抓取的資料。但是,對於大量頁面依序抓取資料可能不是最佳選擇,特別是當頁面之間沒有相互依賴關係時。透過將 parallel
選項指定為 true
,您就能夠平行且獨立地抓取頁面,這可以顯著加快載入過程。
// 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
。
重新驗證特定頁面
請更新到最新版本(≥ 2.2.5)以使用此 API。
useSWRInfinite
的 mutation 的預設行為是重新驗證所有已載入的頁面。但您可能只想重新驗證已變更的特定頁面。您可以將函數傳遞至 revalidate
選項,藉此僅重新驗證特定頁面。
會為每個頁面呼叫 revalidate
函數。
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
進行全域 Mutate
useSWRInfinite
將所有頁面資料儲存到快取中,每個頁面資料都有特殊的快取金鑰,因此您必須在 swr/infinite
中使用 unstable_serialize
,以使用全域 mutate 重新驗證資料。
import { useSWRConfig } from "swr"
import { unstable_serialize } from "swr/infinite"
function App() {
const { mutate } = useSWRConfig()
mutate(unstable_serialize(getKey))
}
顧名思義,unstable_serialize
不是穩定的 API,因此我們未來可能會對其進行變更。
進階功能
這裡有一個範例,說明如何使用 useSWRInfinite
實作下列功能
- 載入狀態
- 如果為空,則顯示特殊 UI
- 如果已到達結尾,則停用「載入更多」按鈕
- 可變更的資料來源
- 重新整理整個清單