跳至内容
宣布 SWR 2.0

宣布 SWR 2.0

2022 年 12 月 9 日 作者 Shu DingJiachi LiuToru KobayashiYixuan Xu

我们很高兴地宣布 SWR 2.0 的发布,SWR 是一个流行的 React 数据获取库,它使组件能够获取、缓存和变异数据,并随着时间的推移通过更改该数据来保持 UI 的更新。

这个新版本充满了改进和新功能,例如新的变异 API、改进的乐观 UI 功能、新的 DevTools 以及对并发渲染的更好支持。我们要向所有为这个版本做出贡献和维护的贡献者表示衷心的感谢。

变异和乐观 UI

useSWRMutation

变异是数据获取过程中的重要组成部分。它们允许您在本地和远程对数据进行更改。我们现有的 mutate API 允许您手动重新验证和变异资源。在 SWR 2.0 中,新的钩子 useSWRMutation 使得使用声明式 API 远程更改数据变得更加简单。您可以使用钩子设置变异,然后在以后激活它

import useSWRMutation from 'swr/mutation'
 
async function sendRequest(url, { arg }) {
  return fetch(url, {
    method: 'POST',
    body: JSON.stringify(arg)
  })
}
 
function App() {
  const { trigger, isMutating } = useSWRMutation('/api/user', sendRequest)
 
  return (
    <button
      disabled={isMutating}
      onClick={() => trigger({ username: 'johndoe' })}
    >{
      isMutating ? 'Creating...' : 'Create User'
    }</button>
  )
}

上面的示例定义了一个 sendRequest 变异,它会影响 '/api/user' 资源。与 useSWR 不同,useSWRMutation 不会在渲染时立即开始请求。相反,它返回一个 trigger 函数,该函数可以稍后被调用以手动启动变异。

当按钮被点击时,sendRequest 函数将被调用,并带有额外的参数 { username: 'johndoe' }isMutating 的值将被设置为 true,直到变异完成。

此外,这个新的钩子解决了您在变异中可能遇到的其他问题

  • 在变异数据时乐观地更新 UI
  • 在变异失败时自动回滚
  • 避免 useSWR 和同一资源的其他变异之间的任何潜在竞争条件
  • 在变异完成后填充 useSWR 缓存
  • ...

您可以在阅读 文档 或滚动浏览接下来的几节中找到深入的 API 参考和示例。

乐观 UI

乐观 UI 是创建快速响应的网站的绝佳模型;但是,正确实现它可能很困难。SWR 2.0 添加了一些强大的新选项,使它更容易。

假设我们有一个 API,它将新的 todo 添加到 todo 列表中并将其发送到服务器

await addNewTodo('New Item')

在我们的 UI 中,我们使用 useSWR 钩子来显示 todo 列表,并使用一个“添加新项目”按钮来触发此请求并要求 SWR 通过 mutate() 重新获取数据

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Display data */}</ul>
 
  <button onClick={async () => {
    await addNewTodo('New Item')
    mutate()
  }}>
    Add New Item
  </button>
</>

但是, await addNewTodo(...) 请求可能非常慢。当它正在进行时,即使我们已经知道新列表的样子,用户仍然看到旧列表。使用新的 optimisticData 选项,我们可以在服务器响应之前乐观地显示新列表

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Display data */}</ul>
 
  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
    })
  }}>
    Add New Item
  </button>
</>

SWR 会立即使用 optimisticData 值更新 data,然后将请求发送到服务器。请求完成后,SWR 将重新验证资源以确保它是最新的。

与许多 API 一样,如果 addNewTodo(...) 请求从服务器返回最新的数据,我们也可以直接显示该结果(而不是开始新的重新验证)!有一个新的 populateCache 选项告诉 SWR 使用变异响应更新本地数据

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Display data */}</ul>
 
  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
      populateCache: true,
    })
  }}>
    Add New Item
  </button>
</>

同时,我们不需要在之后进行其他重新验证,因为响应数据来自真相来源,我们可以使用 revalidate 选项禁用它

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Display data */}</ul>
 
  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
      populateCache: true,
      revalidate: false,
    })
  }}>
    Add New Item
  </button>
</>

最后,如果 addNewTodo(...) 出现异常,我们可以通过将 rollbackOnError 设置为 true(这也是默认选项)来恢复我们刚刚设置的乐观数据([...data, 'New Item'])。当发生这种情况时,SWR 会将 data 回滚到以前的值。

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Display data */}</ul>
 
  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
      populateCache: true,
      revalidate: false,
      rollbackOnError: true,
    })
  }}>
    Add New Item
  </button>
</>

所有这些 API 在新的 useSWRMutation 钩子中也受支持。要了解更多信息,你可以查看我们的 文档。这里有一个演示了该行为的演示。

带有自动错误回滚的乐观 UI

变异多个键

全局 mutate API 现在接受一个过滤器函数,你可以在其中变异或重新验证特定键。这将有助于诸如使所有缓存数据无效之类的用例。要了解更多信息,你可以阅读文档中的 变异多个键

import { mutate } from 'swr'
// Or from the hook if you have customized your cache provider:
// { mutate } = useSWRConfig()
 
// Mutate single resource
mutate(key)
 
// Mutate multiple resources and clear the cache (set to undefined)
mutate(
  key => typeof key === 'string' && key.startsWith('/api/item?id='),
  undefined,
  { revalidate: false }
)

SWR 开发工具

SWRDevTools (opens in a new tab) 是一个浏览器扩展,它可以帮助你调试 SWR 缓存和获取结果。查看我们的 devtools 部分,了解如何在应用程序中使用开发工具。

预加载数据

预加载数据可以极大地改善用户体验。如果你知道资源将在应用程序中稍后使用,你可以使用新的 preload API 早点开始获取。

import useSWR, { preload } from 'swr'
 
const fetcher = (url) => fetch(url).then((res) => res.json())
 
// You can call the preload function in anywhere
preload('/api/user', fetcher)
 
function Profile() {
  // The component that actually uses the data:
  const { data, error } = useSWR('/api/user', fetcher)
  // ...
}
 
export function Page () {
  return <Profile/>
}

在此示例中,preload API 在全局范围内调用。这意味着我们开始在 React 甚至开始渲染任何内容之前预加载资源。当 Profile 组件被渲染时,数据可能已经可用。如果仍在进行中,useSWR 钩子将重用正在进行的预加载请求,而不是启动新的请求。

preload API 也可用于预加载可能将要渲染的另一个页面的数据等情况。有关使用 SWR 预取数据的更多信息,请参阅 此处

isLoading

isLoadinguseSWR 返回的一个新状态,它指示 **请求是否仍在进行中,并且尚未加载任何数据**。以前,isValidating 状态同时表示初始加载状态和重新验证状态,因此我们必须检查 dataerror 是否都为 undefined 来确定它是否是初始加载状态。

现在,你可以直接使用 isLoading 值来渲染加载消息,非常方便。

import useSWR from 'swr'
 
function Profile() {
  const { data, isLoading } = useSWR('/api/user', fetcher)
 
  if (isLoading) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

请注意,isValidating 仍然存在,因此你仍然可以使用它来为重新验证显示加载指示器。

📝

我们添加了新的 了解 SWR 页面来描述 SWR 如何返回值,其中包括 isValidatingisLoading 之间的区别,以及如何将它们结合起来以改善用户体验。

保留先前状态

keepPreviousData 选项是新添加的功能,它允许你保留之前获取的数据。当你在实时发生的用户操作的基础上获取数据时,这极大地改善了 UX,就像使用实时搜索功能一样,其中资源的 key 会不断变化。

function Search() {
  const [search, setSearch] = React.useState('');
 
  const { data, isLoading } = useSWR(`/search?q=${search}`, fetcher, {
    keepPreviousData: true
  })
 
  return (
    <div>
      <input
        type="text"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        placeholder="Search..."
      />
 
      <div className={isLoading ? "loading" : ""}>
        {data?.products.map(item => <Product key={item.id} name={item.name} />)
      </div>
    </div>
  );
}
启用 keepPreviousData 后,保留以前的搜索结果。

查看 CodeSandbox (opens in a new tab) 上的代码,你可以在这里阅读更多相关信息 此处

扩展配置

SWRConfig 现在可以接受一个函数值。当你有多个级别的 <SWRConfig> 时,内部接收父配置并返回一个新的配置。此更改使得在大型代码库中配置 SWR 更加灵活。更多信息可以在这里找到 此处

<SWRConfig
  value={parentConfig => ({
    dedupingInterval: parentConfig.dedupingInterval * 5,
    refreshInterval: 100,
  })}
>
  <Page />
</SWRConfig>

改进的 React 18 支持

SWR 已更新其内部代码以在 React 18 中使用 useSyncExternalStorestartTransition API。这些可以确保在并发渲染 UI 时更强的一致性。此更改不需要任何用户代码更改,所有开发人员都可以直接从中受益。为 React 17 及更低版本包含垫片。

SWR 2.0 和所有新功能仍然与 React 16 和 17 兼容。

迁移指南

获取器不再接受多个参数

key 现在作为单个参数传递。

- useSWR([1, 2, 3], (a, b, c) => {
+ useSWR([1, 2, 3], ([a, b, c]) => {
  assert(a === 1)
  assert(b === 2)
  assert(c === 3)
})

全局变异不再接受 getKey 函数

现在,如果你向全局 mutate 传递一个函数,它将被用作 过滤器。以前,你可以向全局 mutate 传递一个返回键的函数。

- mutate(() => '/api/item') // a function to return a key
+ mutate('/api/item')       // to mutate the key, directly pass it

新的必需属性 keys() 用于缓存接口

当您使用自己的缓存实现时,缓存接口现在需要一个 keys() 方法,该方法返回缓存对象中的所有键,类似于 JavaScript Map 实例。

interface Cache<Data> {
  get(key: string): Data | undefined
  set(key: string, value: Data): void
  delete(key: string): void
+ keys(): IterableIterator<string>
}

更改了缓存内部结构

缓存数据的内部结构将是一个对象,其中包含所有当前状态。

- assert(cache.get(key) === data)
+ assert(cache.get(key) === { data, error, isValidating })
 
// getter
- cache.get(key)
+ cache.get(key)?.data
 
// setter
- cache.set(key, data)
+ cache.set(key, { ...cache.get(key), data })
🚨

您不应该直接写入缓存,这可能会导致未定义的行为。

SWRConfig.default 已重命名为 SWRConfig.defaultValue

SWRConfig.defaultValue 是用于访问默认 SWR 配置的属性。

- SWRConfig.default
+ SWRConfig.defaultValue

类型 InfiniteFetcher 已重命名为 SWRInfiniteFetcher

- import type { InfiniteFetcher } from 'swr/infinite'
+ import type { SWRInfiniteFetcher } from 'swr/infinite'

避免服务器上的 Suspense

如果您想在服务器端使用 SWR 使用 suspense: true,包括在 Next.js 中预渲染,那么您必须通过 fallbackDatafallback 提供初始数据。如今,这意味着您无法使用 Suspense 在服务器端获取数据。您的另外两个选择是在客户端完成数据获取,或让您的框架为您获取数据(例如 Next.js 中的 getStaticProps)。

ES2018 作为构建目标

如果您想支持 IE 11,则必须在您的框架或捆绑器中针对 ES5。此更改已在 SSR 上提高了性能,并保持了捆绑包的大小。

变更日志

阅读完整的变更日志 在 GitHub 上 (在新标签页中打开).

未来 & 感谢!

随着 Next.js 13 (在新标签页中打开) 的新版本发布,我们看到了 React 生态系统中很多令人兴奋的新事物以及范式转变: React 服务器组件 (在新标签页中打开)、流式 SSR、 异步组件 (在新标签页中打开) 以及 use 钩子 (在新标签页中打开)。其中很多与数据获取相关,有些与 SWR 的用例重叠。

但是,SWR 项目的目标保持不变。我们希望它成为一个轻量级、框架无关且略带主观意见(即在焦点时重新验证)的即插即用库。我们不想成为一种标准解决方案,而是希望专注于使 UX 更好的创新。同时,我们还在研究如何利用 React 的这些新功能来改进 SWR。

我们要感谢 143 (在新标签页中打开) 位贡献者(以及 106 (在新标签页中打开) 位文档贡献者),以及帮助我们或提供反馈的每个人。特别感谢 Toru Kobayashi (在新标签页中打开) 为 DevTools 和文档所做的一切工作——没有您,我们不可能做到!