變更與重新驗證
SWR 提供了 mutate
和 useSWRMutation
API 來變更遠端資料和相關快取。
mutate
有兩種方式可以使用 mutate
API 來變更資料,一種是全域變更 API,可以變更任何鍵值,另一種是綁定變更 API,只能變更對應 SWR Hook 的資料。
全域變更
取得全域變更器的建議方法是使用 useSWRConfig
Hook
import { useSWRConfig } from "swr"
function App() {
const { mutate } = useSWRConfig()
mutate(key, data, options)
}
您也可以全域匯入它
import { mutate } from "swr"
function App() {
mutate(key, data, options)
}
僅使用 key
參數的全域變更器不會更新快取或觸發重新驗證,除非有已掛載的 SWR Hook 使用相同的鍵值。
綁定變更
綁定變更是使用資料變更目前鍵值的捷徑。其中 key
綁定到傳遞至 useSWR
的 key
,並接收 data
作為第一個參數。
它的功能與前一節中的全域 mutate
函式相同,但不需要 key
參數
import useSWR from 'swr'
function Profile () {
const { data, mutate } = useSWR('/api/user', fetcher)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
// send a request to the API to update the data
await requestUpdateUsername(newName)
// update the local data immediately and revalidate (refetch)
// NOTE: key is not required when using useSWR's mutate as it's pre-bound
mutate({ ...data, name: newName })
}}>Uppercase my name!</button>
</div>
)
}
重新驗證
當您呼叫 mutate(key)
(或僅使用綁定變更 API 的 mutate()
)而沒有任何資料時,它會觸發資源的重新驗證(將資料標記為過期並觸發重新獲取)。此範例顯示當使用者按下「登出」按鈕時,如何自動重新獲取登入資訊(例如在 <Profile/>
內)。
import useSWR, { useSWRConfig } from 'swr'
function App () {
const { mutate } = useSWRConfig()
return (
<div>
<Profile />
<button onClick={() => {
// set the cookie as expired
document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'
// tell all SWRs with this key to revalidate
mutate('/api/user')
}}>
Logout
</button>
</div>
)
}
它會廣播到相同快取提供者範圍下的 SWR Hook。如果不存在快取提供者,它會廣播到所有 SWR Hook。
API
參數
key
:與useSWR
的key
相同,但函式的行為如同篩選函式data
:用於更新用戶端快取的資料,或是用於遠端變更的非同步函式options
:接受以下選項optimisticData
:立即更新用戶端快取的資料,或是接收目前資料並傳回新的用戶端快取資料的函式,通常用於樂觀 UI。revalidate = true
:非同步更新解決後是否應該重新驗證快取。如果設定為函式,則該函式會接收data
和key
。populateCache = true
:是否將遠端變更的結果寫入快取,或是一個函式,該函式接收新的結果和當前結果作為引數,並返回變更結果。rollbackOnError = true
:如果遠端變更發生錯誤,是否應回滾快取,或是一個函式,該函式接收從 fetcher 拋出的錯誤作為引數,並返回一個布林值,指示是否應該回滾。throwOnError = true
:當失敗時,變更呼叫是否應該拋出錯誤。
返回值
mutate
返回 data
參數解析後的結果。傳遞給 mutate
的函式將返回更新後的資料,該資料用於更新相應的快取值。如果在執行函式時拋出錯誤,則會拋出該錯誤,以便可以適當處理。
try {
const user = await mutate('/api/user', updateUser(newUser))
} catch (error) {
// Handle an error while updating the user here
}
useSWRMutation
SWR 還提供 useSWRMutation
作為遠端變更的 Hook。遠端變更僅以手動方式觸發,而不是像 useSWR
一樣自動觸發。
此外,此 Hook 不會與其他 useSWRMutation
Hook 共享狀態。
import useSWRMutation from 'swr/mutation'
// Fetcher implementation.
// The extra argument will be passed via the `arg` property of the 2nd parameter.
// In the example below, `arg` will be `'my_token'`
async function updateUser(url, { arg }: { arg: string }) {
await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${arg}`
}
})
}
function Profile() {
// A useSWR + mutate like API, but it will not start the request automatically.
const { trigger } = useSWRMutation('/api/user', updateUser, options)
return <button onClick={() => {
// Trigger `updateUser` with a specific argument.
trigger('my_token')
}}>Update User</button>
}
API
參數
key
:與mutate
的key
相同fetcher(key, { arg })
:用於遠端變更的非同步函式options
:具有以下屬性的可選物件optimisticData
:與mutate
的optimisticData
相同revalidate = true
:與mutate
的revalidate
相同populateCache = false
:與mutate
的populateCache
相同,但預設值為false
rollbackOnError = true
:與mutate
的rollbackOnError
相同throwOnError = true
:與mutate
的throwOnError
相同onSuccess(data, key, config)
: 遠端變更成功完成時的回呼函式onError(err, key, config)
:遠端變更返回錯誤時的回呼函式
返回值
data
:從fetcher
返回的給定鍵的資料error
:由fetcher
拋出的錯誤(或未定義)trigger(arg, options)
:用於觸發遠端變更的函式reset
:用於重置狀態的函式(data
、error
、isMutating
)isMutating
:如果存在正在進行的遠端變更
基本用法
import useSWRMutation from 'swr/mutation'
async function sendRequest(url, { arg }: { arg: { username: string }}) {
return fetch(url, {
method: 'POST',
body: JSON.stringify(arg)
}).then(res => res.json())
}
function App() {
const { trigger, isMutating } = useSWRMutation('/api/user', sendRequest, /* options */)
return (
<button
disabled={isMutating}
onClick={async () => {
try {
const result = await trigger({ username: 'johndoe' }, /* options */)
} catch (e) {
// error handling
}
}}
>
Create User
</button>
)
}
如果您想在渲染中使用變更結果,可以從 useSWRMutation
的返回值中取得它們。
const { trigger, data, error } = useSWRMutation('/api/user', sendRequest)
useSWRMutation
與 useSWR
共用快取儲存空間,因此它可以偵測並避免 useSWR
之間的競爭條件。它還支援 mutate
的功能,例如樂觀更新和錯誤回滾。您可以將這些選項傳遞給 useSWRMutation
及其 trigger
函式。
const { trigger } = useSWRMutation('/api/user', updateUser, {
optimisticData: current => ({ ...current, name: newName })
})
// or
trigger(newName, {
optimisticData: current => ({ ...current, name: newName })
})
延遲載入資料直到需要時
您也可以使用 useSWRMutation
來載入資料。useSWRMutation
不會開始請求,直到 trigger
被呼叫,因此您可以在實際需要時延遲載入資料。
import { useState } from 'react'
import useSWRMutation from 'swr/mutation'
const fetcher = url => fetch(url).then(res => res.json())
const Page = () => {
const [show, setShow] = useState(false)
// data is undefined until trigger is called
const { data: user, trigger } = useSWRMutation('/api/user', fetcher);
return (
<div>
<button onClick={() => {
trigger();
setShow(true);
}}>Show User</button>
{show && user ? <div>{user.name}</div> : null}
</div>
);
}
樂觀更新
在許多情況下,對資料應用本機變更是一個讓變更感覺更快速的好方法,無需等待遠端資料來源。
使用 optimisticData
選項,您可以在等待遠端變更完成時,手動更新您的本機資料。組合 rollbackOnError
,您也可以控制何時回滾資料。
import useSWR, { useSWRConfig } from 'swr'
function Profile () {
const { mutate } = useSWRConfig()
const { data } = useSWR('/api/user', fetcher)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
const user = { ...data, name: newName }
const options = {
optimisticData: user,
rollbackOnError(error) {
// If it's timeout abort error, don't rollback
return error.name !== 'AbortError'
},
}
// updates the local data immediately
// send a request to update the data
// triggers a revalidation (refetch) to make sure our local data is correct
mutate('/api/user', updateFn(user), options);
}}>Uppercase my name!</button>
</div>
)
}
updateFn
應該是一個 Promise 或非同步函式,用於處理遠端變更,它應該返回更新後的資料。
您也可以傳遞一個函式給 optimisticData
,使其取決於目前的資料
import useSWR, { useSWRConfig } from 'swr'
function Profile () {
const { mutate } = useSWRConfig()
const { data } = useSWR('/api/user', fetcher)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
mutate('/api/user', updateUserName(newName), {
optimisticData: user => ({ ...user, name: newName }),
rollbackOnError: true
});
}}>Uppercase my name!</button>
</div>
)
}
您也可以使用 useSWRMutation
和 trigger
建立相同的效果
import useSWRMutation from 'swr/mutation'
function Profile () {
const { trigger } = useSWRMutation('/api/user', updateUserName)
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={async () => {
const newName = data.name.toUpperCase()
trigger(newName, {
optimisticData: user => ({ ...user, name: newName }),
rollbackOnError: true
})
}}>Uppercase my name!</button>
</div>
)
}
錯誤時回滾
當您設定了 optimisticData
時,樂觀資料有可能會顯示給使用者,但遠端變更失敗。在這種情況下,您可以利用 rollbackOnError
將本機快取還原到先前的狀態,以確保使用者看到的是正確的資料。
變更後更新快取
有時,遠端變更請求會直接返回更新後的資料,因此無需進行額外的擷取來載入。您可以啟用 populateCache
選項,以使用變更的回應更新 useSWR
的快取
const updateTodo = () => fetch('/api/todos/1', {
method: 'PATCH',
body: JSON.stringify({ completed: true })
})
mutate('/api/todos', updateTodo, {
populateCache: (updatedTodo, todos) => {
// filter the list, and return it with the updated item
const filteredTodos = todos.filter(todo => todo.id !== '1')
return [...filteredTodos, updatedTodo]
},
// Since the API already gives us the updated information,
// we don't need to revalidate here.
revalidate: false
})
或使用 useSWRMutation
Hook
useSWRMutation('/api/todos', updateTodo, {
populateCache: (updatedTodo, todos) => {
// filter the list, and return it with the updated item
const filteredTodos = todos.filter(todo => todo.id !== '1')
return [...filteredTodos, updatedTodo]
},
// Since the API already gives us the updated information,
// we don't need to revalidate here.
revalidate: false
})
當與 optimisticData
和 rollbackOnError
結合使用時,您將獲得完美的樂觀 UI 體驗。
避免競爭條件
mutate
和 useSWRMutation
都可以避免 useSWR
之間的競爭條件。例如,
function Profile() {
const { data } = useSWR('/api/user', getUser, { revalidateInterval: 3000 })
const { trigger } = useSWRMutation('/api/user', updateUser)
return <>
{data ? data.username : null}
<button onClick={() => trigger()}>Update User</button>
</>
}
一般的 useSWR
Hook 可能會由於焦點、輪詢或其他條件而隨時刷新其資料。這樣一來,顯示的使用者名稱就可以盡可能的新。但是,由於我們有一個變更可能會與 useSWR
的重新擷取幾乎同時發生,因此可能會出現競爭條件,即 getUser
請求較早開始,但花費的時間比 updateUser
長。
幸運的是,useSWRMutation
會自動為您處理此問題。變更後,它會告知 useSWR
捨棄進行中的請求並重新驗證,因此永遠不會顯示過時的資料。
根據目前資料變更
有時,您想要根據目前資料更新部分資料。
使用 mutate
,您可以傳遞一個非同步函式,該函式將接收目前的快取值(如果有的話),並返回更新的文件。
mutate('/api/todos', async todos => {
// let's update the todo with ID `1` to be completed,
// this API returns the updated data
const updatedTodo = await fetch('/api/todos/1', {
method: 'PATCH',
body: JSON.stringify({ completed: true })
})
// filter the list, and return it with the updated item
const filteredTodos = todos.filter(todo => todo.id !== '1')
return [...filteredTodos, updatedTodo]
// Since the API already gives us the updated information,
// we don't need to revalidate here.
}, { revalidate: false })
變更多個項目
全域 mutate
API 接受一個篩選函式,該函式接受 key
作為引數,並返回要重新驗證的鍵。篩選函式會應用於所有現有的快取鍵
import { mutate } from 'swr'
// Or from the hook if you customized the cache provider:
// { mutate } = useSWRConfig()
mutate(
key => typeof key === 'string' && key.startsWith('/api/item?id='),
undefined,
{ revalidate: true }
)
這也適用於任何鍵類型,例如陣列。變更會比對所有鍵,其中第一個元素是 'item'
。
useSWR(['item', 123], ...)
useSWR(['item', 124], ...)
useSWR(['item', 125], ...)
mutate(
key => Array.isArray(key) && key[0] === 'item',
undefined,
{ revalidate: false }
)
篩選函式會應用於所有現有的快取鍵,因此在使用多種鍵形狀時,您不應假設鍵的形狀。
// ✅ matching array key
mutate((key) => key[0].startsWith('/api'), data)
// ✅ matching string key
mutate((key) => typeof key === 'string' && key.startsWith('/api'), data)
// ❌ ERROR: mutate uncertain keys (array or string)
mutate((key: any) => /\/api/.test(key.toString()))
您可以使用篩選函式清除所有快取資料,這在登出時很有用
const clearCache = () => mutate(
() => true,
undefined,
{ revalidate: false }
)
// ...clear cache on logout
clearCache()