跳至內容
文件
變更與重新驗證

變更與重新驗證

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 返回 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 拋出的錯誤(或未定義)
  • 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 來載入資料。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>
  )
}

您也可以使用 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 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 體驗。

避免競爭條件

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