跳至内容
文档
变异与重新验证

变异与重新验证

SWR 提供了 mutateuseSWRMutation 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 与传递给 useSWRkey 绑定,并接收 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: 与 useSWRkey 相同,但函数的行为类似于 一个过滤器函数
  • data: 用于更新客户端缓存的数据,或者用于远程变异的异步函数
  • options: 接受以下选项
    • optimisticData: 用于立即更新客户端缓存的数据,或接收当前数据并返回新客户端缓存数据的函数,通常用于乐观 UI。
    • revalidate = true: 异步更新完成后是否应重新验证缓存。如果设置为函数,该函数接收 datakey
    • 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: 与 mutatekey 相同
  • fetcher(key, { arg }): 用于远程突变的异步函数
  • options: 一个包含以下属性的可选对象
    • optimisticData: 与 mutateoptimisticData 相同
    • revalidate = true: 与 mutaterevalidate 相同
    • populateCache = false: 与 mutatepopulateCache 相同,但默认值为 false
    • rollbackOnError = true: 与 mutaterollbackOnError 相同
    • throwOnError = true: 与 mutatethrowOnError 相同
    • onSuccess(data, key, config): 远程突变成功完成时的回调函数
    • onError(err, key, config): 远程突变返回错误时的回调函数

返回值

  • data: fetcher 返回的给定键的数据
  • error: fetcher 抛出的错误(或 undefined)
  • trigger(arg, options): 用于触发远程突变的函数
  • reset: 用于重置状态的函数(dataerrorisMutating
  • 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)

useSWRMutationuseSWR 共享一个缓存存储,因此可以检测并避免 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>
  )
}

您也可以使用 useSWRMutationtrigger 来创建相同的内容

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
})

当与 optimisticDatarollbackOnError 结合使用时,您将获得完美的乐观 UI 体验。

避免竞争条件

mutateuseSWRMutation 都可以避免 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()