Skip to content
开发文档
修改和重新验证

修改和重新验证

¥Mutation & Revalidation

SWR 提供 mutateuseSWRMutation API 用于改变远程数据和相关缓存。

¥SWR provides the mutate and useSWRMutation APIs for mutating remote data and related cache.

mutate

有两种方法可以使用 mutate API 来改变数据,全局 mutate API 可以改变任何键,绑定 mutate API 只能改变相应 SWR 钩子的数据。

¥There're 2 ways to use the mutate API to mutate the data, the global mutate API which can mutate any key and the bound mutate API which only can mutate the data of corresponding SWR hook.

全局修改

¥Global Mutate

获取全局变量的推荐方法是使用 useSWRConfig 钩子:

¥The recommended way to get the global mutator is to use the useSWRConfig hook:

import { useSWRConfig } from "swr"
 
function App() {
  const { mutate } = useSWRConfig()
  mutate(key, data, options)
}

你还可以全局导入它:

¥You can also import it globally:

import { mutate } from "swr"
 
function App() {
  mutate(key, data, options)
}
⚠️

仅将全局变元与 key 参数一起使用将不会更新缓存或触发重新验证,除非存在使用相同键的已安装 SWR 钩子。

¥Using global mutator only with the key parameter will not update the cache or trigger revalidation unless there is a mounted SWR hook using the same key.

绑定修改

¥Bound Mutate

绑定修改是用数据改变当前键的短路径。其中 key 绑定到传递给 useSWRkey,并接收 data 作为第一个参数。

¥Bound mutate is the short path to mutate the current key with data. Which key is bounded to the key passing to useSWR, and receive the data as the first argument.

它在功能上等同于上一节中的全局 mutate 函数,但不需要 key 参数:

¥It is functionally equivalent to the global mutate function in the previous section but does not require the key parameter:

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

重新验证

¥Revalidation

当你在没有任何数据的情况下调用 mutate(key)(或仅使用绑定的 mutate API 调用 mutate())时,它将触发资源的重新验证(将数据标记为过期并触发重新获取)。此示例展示了当用户单击“注销”按钮时如何自动重新获取登录信息(例如在 <Profile/> 内):

¥When you call mutate(key) (or just mutate() with the bound mutate API) without any data, it will trigger a revalidation (mark the data as expired and trigger a refetch) for the resource. This example shows how to automatically refetch the login info (e.g. inside <Profile/>) when the user clicks the “Logout” button:

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 钩子。如果不存在缓存提供程序,它将广播到所有 SWR 钩子。

¥It broadcasts to SWR hooks under the same cache provider scope. If no cache provider exists, it will broadcast to all SWR hooks.

API

参数

¥Parameters

  • key:与 useSWRkey 相同,但函数的行为与 过滤函数 相同

    ¥key: same as useSWR's key, but a function behaves as a filter function

  • data:用于更新客户端缓存的数据,或用于远程突变的异步函数

    ¥data: data to update the client cache, or an async function for the remote mutation

  • options:接受以下选项

    ¥options: accepts the following options

    • optimisticData:data 立即更新客户端缓存,或者接收当前数据并返回新的客户端缓存数据的函数,通常用在乐观 UI 中。

      ¥optimisticData: data to immediately update the client cache, or a function that receives current data and returns the new client cache data, usually used in optimistic UI.

    • revalidate = true:一旦异步更新解决,缓存是否应该重新验证。

      ¥revalidate = true: should the cache revalidate once the asynchronous update resolves.

    • populateCache = true:远程突变的结果是否应该写入缓存,或者接收新结果和当前结果作为参数并返回突变结果的函数。

      ¥populateCache = true: should the result of the remote mutation be written to the cache, or a function that receives new result and current result as arguments and returns the mutation result.

    • rollbackOnError = true:如果远程突变错误,是否应该回滚缓存,或者接收从 fetcher 抛出的错误作为参数并返回是否应该回滚的布尔值的函数。

      ¥rollbackOnError = true: should the cache rollback if the remote mutation errors, or a function that receives the error thrown from fetcher as arguments and returns a boolean whether should rollback or not.

    • throwOnError = true:mutate 调用失败时是否应该抛出错误。

      ¥throwOnError = true: should the mutate call throw the error when fails.

返回值

¥Return Values

mutate 返回 data 参数已解析的结果。传递给 mutate 的函数将返回更新后的数据,该数据用于更新相应的缓存值。如果在执行函数时抛出错误,则会抛出该错误,以便进行适当的处理。

¥mutate returns the results the data parameter has been resolved. The function passed to mutate will return an updated data which is used to update the corresponding cache value. If there is an error thrown while executing the function, the error will be thrown so it can be handled appropriately.

try {
  const user = await mutate('/api/user', updateUser(newUser))
} catch (error) {
  // Handle an error while updating the user here
}

useSWRMutation

SWR 还提供 useSWRMutation 作为远程突变的钩子。远程突变只能手动触发,而不是像 useSWR 那样自动触发。

¥SWR also provides useSWRMutation as a hook for remote mutations. The remote mutations are only triggered manually, instead of automatically like useSWR.

此外,此钩子不与其他 useSWRMutation 钩子共享状态。

¥Also, this hook doesn’t share states with other useSWRMutation hooks.

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

参数

¥Parameters

  • key:与 mutatekey 相同

    ¥key: same as mutate's key

  • fetcher(key, { arg }):用于远程突变的异步函数

    ¥fetcher(key, { arg }): an async function for remote mutation

  • options:具有以下属性的可选对象:

    ¥options: an optional object with the following properties:

    • optimisticData:与 mutateoptimisticData 相同

      ¥optimisticData: same as mutate's optimisticData

    • revalidate = true:与 mutaterevalidate 相同

      ¥revalidate = true: same as mutate's revalidate

    • populateCache = false:与 mutatepopulateCache 相同,但默认为 false

      ¥populateCache = false: same as mutate's populateCache, but the default is false

    • rollbackOnError = true:与 mutaterollbackOnError 相同

      ¥rollbackOnError = true: same as mutate's rollbackOnError

    • throwOnError = true:与 mutatethrowOnError 相同

      ¥throwOnError = true: same as mutate's throwOnError

    • onSuccess(data, key, config):远程突变成功完成时的回调函数

      ¥onSuccess(data, key, config):  callback function when a remote mutation has been finished successfully

    • onError(err, key, config):远程突变返回错误时的回调函数

      ¥onError(err, key, config): callback function when a remote mutation has returned an error

返回值

¥Return Values

  • data:从 fetcher 返回给定键的数据

    ¥data: data for the given key returned from fetcher

  • errorfetcher 抛出的错误(或未定义)

    ¥error: error thrown by fetcher (or undefined)

  • trigger(arg, options):触发远程突变的函数

    ¥trigger(arg, options): a function to trigger a remote mutation

  • reset:重置状态的函数(dataerrorisMutating

    ¥reset: a function to reset the state (data, error, isMutating)

  • isMutating:如果存在持续的远程突变

    ¥isMutating: if there's an ongoing remote mutation

基本用法

¥Basic Usage

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 的返回值中获取它们。

¥If you want to use the mutation results in rendering, you can get them from the return values of useSWRMutation.

const { trigger, data, error } = useSWRMutation('/api/user', sendRequest)

useSWRMutationuseSWR 共享缓存存储,因此它可以检测并避免 useSWR 之间的竞争条件。它还支持 mutate 的功能,例如乐观更新和错误回滚。你可以传递这些选项 useSWRMutation 及其 trigger 功能。

¥useSWRMutation shares a cache store with useSWR, so it can detect and avoid race conditions between useSWR. It also supports mutate's functionalities like optimistic updates and rollback on errors. You can pass these options useSWRMutation and its trigger function.

const { trigger } = useSWRMutation('/api/user', updateUser, {
  optimisticData: current => ({ ...current, name: newName })
})
 
// or
 
trigger(newName, {
  optimisticData: current => ({ ...current, name: newName })
})

推迟加载数据直到需要时

¥Defer loading data until needed

你还可以使用 useSWRMutation 加载数据。useSWRMutation 在调用 trigger 之前不会开始请求,因此你可以在实际需要时推迟加载数据。

¥You can also use useSWRMutation for loading data. useSWRMutation won't start requesting until trigger is called, so you can defer loading data when you actually need it.

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

乐观的更新

¥Optimistic Updates

在许多情况下,对数据应用本地突变是一种让更改感觉更快的好方法 - 无需等待远程数据源。

¥In many cases, applying local mutations to data is a good way to make changes feel faster — no need to wait for the remote source of data.

使用 optimisticData 选项,你可以手动更新本地数据,同时等待远程突变完成。编写 rollbackOnError 你还可以控制何时回滚数据。

¥With the optimisticData option, you can update your local data manually, while waiting for the remote mutation to finish. Composing rollbackOnError you can also control when to rollback the data.

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 或异步函数来处理远程突变,它应该返回更新的数据。

¥The updateFn should be a promise or asynchronous function to handle the remote mutation, it should return updated data.

你还可以向 optimisticData 传递一个函数,使其取决于当前数据:

¥You can also pass a function to optimisticData to make it depending on the current data:

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 创建相同的内容:

¥You can also create the same thing with useSWRMutation and 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>
  )
}

错误回滚

¥Rollback on Errors

当你设置了 optimisticData 时,有可能向用户显示乐观的数据,但远程修改失败。在这种情况下,你可以利用 rollbackOnError 将本地缓存恢复到之前的状态,以确保用户看到正确的数据。

¥When you have optimisticData set, it’s possible that the optimistic data gets displayed to the user, but the remote mutation fails. In this case, you can leverage rollbackOnError to revert the local cache to the previous state, to make sure the user is seeing the correct data.

修改后更新缓存

¥Update Cache After Mutation

有时,远程修改请求会直接返回更新后的数据,因此无需执行额外的 fetch 来加载它。你可以启用 populateCache 选项以使用突变响应更新 useSWR 的缓存:

¥Sometimes, the remote mutation request directly returns the updated data, so there is no need to do an extra fetch to load it. You can enable the populateCache option to update the cache for useSWR with the response of the mutation:

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 钩子:

¥Or with the 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
})

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

¥When combined with optimisticData and rollbackOnError, you’ll get a perfect optimistic UI experience.

避免竞争条件

¥Avoid Race Conditions

mutateuseSWRMutation 都可以避免 useSWR 之间的竞争条件。例如,

¥Both mutate and useSWRMutation can avoid race conditions between useSWR. For example,

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 长。

¥The normal useSWR hook might refresh its data any time due to focus, polling, or other conditions. This way the displayed username can be as fresh as possible. However, since we have a mutation there that can happen at the nearly same time of a refetch of useSWR, there could be a race condition that getUser request starts earlier, but takes longer than updateUser.

幸运的是,useSWRMutation 会自动为你处理这个问题。突变后,它将告诉 useSWR 放弃正在进行的请求并重新验证,因此过时的数据将永远不会显示。

¥Luckily, useSWRMutation handles this for you automatically. After the mutation, it will tell useSWR to ditch the ongoing request and revalidate, so the stale data will never be displayed.

根据当前数据进行修改

¥Mutate Based on Current Data

有时,你希望根据当前数据更新部分数据。

¥Sometimes, you want to update a part of your data based on the current data.

使用 mutate,你可以传递一个异步函数,该函数将接收当前缓存的值(如果有),并返回更新的文档。

¥With mutate, you can pass an async function which will receive the current cached value, if any, and returns an updated document.

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 Multiple Items

全局 mutate API 接受一个过滤器函数,该函数接受 key 作为参数并返回要重新验证的键。过滤函数应用于所有现有的缓存键:

¥The global mutate API accepts a filter function, which accepts key as the argument and returns which keys to revalidate. The filter function is applied to all the existing cache keys:

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'

¥This also works with any key type like an array. The mutation matches all keys, of which the first element is 'item'.

useSWR(['item', 123], ...)
useSWR(['item', 124], ...)
useSWR(['item', 125], ...)
 
mutate(
  key => Array.isArray(key) && key[0] === 'item',
  undefined,
  { revalidate: false }
)

过滤功能适用于所有现有的缓存键,因此在使用多种形状的键时,不应假设键的形状。

¥The filter function is applied to all existing cache keys, so you should not assume the shape of keys when using multiple shapes of keys.

// ✅ 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()))

你可以使用过滤功能清除所有缓存数据,这在注销时很有用:

¥You can use the filter function to clear all cache data, which is useful when logging out:

const clearCache = () => mutate(
  () => true,
  undefined,
  { revalidate: false }
)
 
// ...clear cache on logout
clearCache()