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 用于演示这种行为:
多个 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
isLoading
是 useSWR
返回的一个新的状态,它表示请求仍在进行中,并且还没有加载数据。在之前,isValidating
状态同时代表初始加载状态和重新验证状态,所以我们必须检查 data
和 error
是否都为 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 如何返回值,其中包括 isValidating
和 isLoading
的区别,以及如何结合它们来改善用户体验。
保存以前的状态
新增加了 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>
);
}
你可以在 CodeSandbox 上查看完整代码,在这里了解更多信息。
扩展配置
SWRConfig
现在可以接受一个函数值。当你有多级的 <SWRConfig>
时,内部会接收父级配置并返回一个新的配置。这一变化使得在大型代码库中配置 SWR 更加灵活。更多信息可以在这里找到。
<SWRConfig
value={parentConfig => ({
dedupingInterval: parentConfig.dedupingInterval * 5,
refreshInterval: 100,
})}
>
<Page />
</SWRConfig>
优化了对 React 18 的支持
SWR 已经更新了其内部代码,以便在 React 18 中使用 useSyncExternalStore
和 startTransition
这两个 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 的预渲染,那么你必须通过 fallbackData
或 fallback
提供初始数据。今天这意味着你不能在服务端使用 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 和文档方面所做的工作--没有你,我们不可能做到这一点!