Skip to content
SWR 2.0 发布

SWR 2.0 发布

我们很高兴宣布 SWR 2.0 的发布,SWR 是一款主流的 React 数据请求库,它使组件能够获取、缓存及更改数据,并使 UI 能随时间变化保持最新的数据。

在新版本中包含了很多优化和新功能,例如新的数据更改 API、经过优化的乐观 UI 功能、新的 DevTools、以及对并发渲染的更好支持。衷心感谢所有使这个版本成为可能的贡献者和维护者。

数据更改和乐观 UI

useSWRMutation

数据更改(Mutation)是数据请求过程中非常重要的一部分,它使你可以修改本地或者远程的数据。我们目前使用的 mutate API 允许你手动重新验证或更新资源。在 SWR 2.0 中,新的 hook useSWRMutation 使用了声明式的 API ,使更改远程数据更加简单。你可以使用 hook 设置一个数据更改,并在随后触发它。

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

上面的例子中定义了一个影响 /api/user 资源的 sendRequest 数据更改。与 useSWR 不同,useSWRMutation 并不会在渲染时立即发送请求,而是会返回一个 trigger 函数,之后你可以手动调用该函数触发数据更改。

sendRequest 函数会在按钮被点击时触发,并传入额外的参数 { username: 'johndoe' }isMutating 的值会被设置为 true 直到数据更改的请求完成。

此外,这个新的 hook 还解决了数据更改可能带来的其他问题:

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

通过阅读 文档 或阅读接下来的几节,你可以找到更加深入的 API 参考和示例。

乐观 UI

乐观 UI 是一种优秀的模型,用于创建让人感觉快速和敏捷的网站;然而,它可能很难正确的去实现。SWR 2.0 增加了一些强大的新选项,使乐观 UI 的实现更加容易。

假设我们有一个 API 用于添加一个新的待办事项到待办列表中,并将它发送到服务端:

await addNewTodo('New Item')

在我们的 UI 中,我们使用 useSWR 这个 hook 去展示待办列表,其中的 "Add New Item" 按钮用于触发这个请求并要求 SWR 通过 mutate() 重新获取数据:

const { mutate, data } = useSWR('/api/todos')

return <>
  <ul>{/* 展示数据 */}</ul>

  <button onClick={async () => {
    await addNewTodo('New Item')
    mutate()
  }}>
    Add New Item
  </button>
</>

然而,await addNewTodo(...) 请求可能会特别慢。当它正在请求时,用户看到仍然是旧的列表,即使我们已经知道新的列表会是什么样子。有了新的 optimisticData 选项,我们可以在服务端响应之前,乐观地展示新的列表:

const { mutate, data } = useSWR('/api/todos')

return <>
  <ul>{/* 展示数据 */}</ul>

  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
    })
  }}>
    Add New Item
  </button>
</>

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

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

const { mutate, data } = useSWR('/api/todos')

return <>
  <ul>{/* 展示数据 */}</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>{/* 展示数据 */}</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>{/* 展示数据 */}</ul>

  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
      populateCache: true,
      revalidate: false,
      rollbackOnError: true,
    })
  }}>
    Add New Item
  </button>
</>

所有 API 都在新的 useSWRMutationhook hook 中得到支持。想要了解更多关于它们的信息,你可以查看我们的 文档,这里有一个 demo 用于演示这种行为:

能自动错误回滚的乐观用户 UI

多个 key 数据更改

全局 mutate API 现在接受一个 filter 函数,您可以在其中进行特定 key 的数据更改或重新验证。这对于”使所有缓存的数据失效”之类的用例非常有帮助。要了解更多信息,可以阅读文档中的 更改多项数据

import { mutate } from 'swr'
// 或者从 hook 获取,如果你自定义了你的缓存 provider:
// { mutate } = useSWRConfig()

// 单数据源数据更改
mutate(key)

// 更改多个数据源数据并清除缓存(设置为 undefined)
mutate(
  key => typeof key === 'string' && key.startsWith('/api/item?id='),
  undefined,
  { revalidate: false }
)

SWR 开发者工具

SWRDevTools 是一个用于帮助你调试你的 SWR 缓存及请求结果的浏览器拓展程序。请查看我们的 devtools 查结,了解如何在你的应用程序中使用 devtools。

预加载数据

预加载数据可以极大地改善用户体验。如果你知道某个资源稍后将在应用程序被使用,那么你可以使用新的 preload API 提前开始请求它:

import useSWR, { preload } from 'swr'

const fetcher = (url) => fetch(url).then((res) => res.json())

// 你可以在任意位置调用 preload 函数
preload('/api/user', fetcher)

function Profile() {
  // 实际使用数据的组件:
  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 选项,它允许你保留之前获取的数据。当你根据实时的用户操作获取数据时可以极大地改善用户体验。例如在实时搜索功能中,资源的 `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 上查看完整代码,在这里了解更多信息。

扩展配置

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

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

优化了对 React 18 的支持

SWR 已经更新了其内部代码,以便在 React 18 中使用 useSyncExternalStorestartTransition 这两个 API。这确保了在并发渲染 UI 时的强一致性。这一变化不需要修改任何用户代码,所有开发者都可以直接受益。

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

迁移指南

Fetcher 不再接受多个参数

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 传递一个返回 key 的函数。而现在,如果你向全局 mutate 传入一个函数,它会被当作一个过滤函数

- mutate(() => '/api/item') // 一个返回 key 的函数
+ mutate('/api/item')       // 直接传入 key 以更改数据

Cache Interface 新增必选属性 keys()

当你使用自己的缓存实现时,Cache 接口新增了一个 keys() 方法来返回缓存对象中的所有 key,类似于 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

Type InfiniteFetcher 重命名为 SWRInfiniteFetcher

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

避免在服务端使用 Suspense

如果你想要在服务端使用 SWR suspense: true,包括 Next.js 的预渲染,那么你必须通过 fallbackDatafallback 提供初始数据。今天这意味着你不能在服务端使用 Susense 获取数据了。你的另外两种选则是完全在客户端获取数据,或者使用框架获取数据(就像 Next.js 中的 getStaticProps 那样)。

以 ES2018 为构建目标

如果你想支持 IE 11,你必须在你的框架或 bundler 指定构建目标为 ES5。这一变化使 SSR 的性能得到了优化,并使构建产物的体积变小。

更新日志

GitHub 查看完整日志。

展望未来 & 致谢!

随着 Next.js 13 的新发布 ,我们看到了很多令人兴奋的新特性和 React 生态系统中的范式转变:React Server Components,streaming SSR,async components,以及 use hook 。其中许多都与数据获取有关,有一部分与 SWR 有着相同的使用场景。

然而 SWR 项目的目标仍然不变。我们希望它成为一个轻量级的、框架无关的、有一点 固执(例如:聚焦时重新验证)的落地库。我们不想成为一个标准的解决方案,而是想专注于创新,使用户体验变得更好。同时我们也在研究如何利用 React 的这些新能力来改进 SWR。

我们要感谢 143 贡献者(+106 文档贡献者)中的每一位,以及那些给我们提供帮助或反馈的人。特别感谢 Toru Kobayashi 在 DevTools 和文档方面所做的工作--没有你,我们不可能做到这一点!