变异与重新验证
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 调用失败时是否应抛出错误。
返回值
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
抛出的错误(或 undefined)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
来加载数据。在调用 trigger
之前,useSWRMutation
不会开始请求,因此您可以在真正需要时推迟加载数据。
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
钩子
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
钩子可能会由于焦点、轮询或其他条件而随时刷新其数据。这样,显示的用户名就可以尽可能新鲜。但是,由于我们有一个变异在那里,它可能在 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()