SWR 简介:用于远程数据获取的 React Hooks
已发表: 2022-03-10SWR 是由 Vercel(前身为 ZEIT)创建的轻量级库,允许使用 React Hooks 实时获取、缓存或重新获取数据。 它是用 React Suspense 构建的,它让你的组件在渲染之前“等待”某些东西,包括数据。 SWR 还附带了一些很棒的功能,例如依赖获取、专注于重新验证、滚动位置恢复等。 它也是一个非常强大的工具,因为它与后端无关并且对 TypeScript 有很好的支持。 这是一个前景光明的包裹。
你为什么要关心? 您应该关心您是否一直在寻找一个不仅可以从 API 获取数据而且还可以执行缓存和依赖获取等操作的库。 当构建具有大量移动部件的 React 应用程序时,本教程将介绍的内容将派上用场。 预计您应该使用过 Axios 和 Fetch API,尽管我们将比较它们与 SWR 的不同之处,但我们不会详细介绍它们将如何实现。
在本指南中,我将通过构建一个从 Pokemon API 请求数据的 Pokedex 应用程序向您介绍 React Hooks for Remote Data Fetching。 我们还将深入探讨 SWR 附带的其他功能,并强调其与 Fetch API 和 Axios 库等流行解决方案相比的差异,并为您提供使用该库的原因以及为什么您应该关注 SWR。
那么,让我们从回答一个基本问题开始:什么是 SWR?
什么是 SWR?
SWR 是 stale-while-revalidate 的缩写。 这是一个用于远程数据获取的 React Hooks 库。 SWR 使用三个主要步骤:首先,它从缓存中返回数据(陈旧部分),然后发送获取请求(重新验证部分),最后带来最新数据。 但不用担心,SWR 会为我们处理所有这些步骤。 我们唯一要做的就是给useSWR
钩子提供发出请求所需的参数。
SWR 还有一些不错的功能,例如:
- 后端不可知论者
- 快速页面导航
- 重新验证焦点
- 间隔轮询
- 请求重复数据删除
- 局部突变
- 分页
- 准备好打字稿
- SSR 支持
- 悬疑模式
- 反应原生支持
- 轻的。
听起来很神奇? 好吧,SWR 简化了事情并增加了你的 React 应用程序的用户体验。 一旦我们开始在我们的项目中实现它,你就会明白为什么这个钩子很方便。
重要的是要知道包的名称是swr
或 SWR ,用于获取 SWR 功能的挂钩名为useSWR
。
理论上,SWR 可能是您增强数据获取所需的。 但是,我们已经有两种在我们的应用程序中发出 HTTP 请求的好方法:Fetch API 和 Axios 库。
那么,为什么要使用新库来检索数据呢? 让我们尝试在下一节中回答这个合法的问题。
与 Fetch 和 Axios 的比较
我们已经有很多方法可以在我们的 React 应用程序中发出 HTTP 请求,其中最流行的两种是 Fetch API 和 Axios 库。 它们都很棒,可以让我们轻松地获取或发送数据。 但是,一旦操作完成,它们不会帮助我们缓存或分页数据,您必须自己做。
Axios 或 Fetch 只会处理请求并返回预期的响应,仅此而已。
与 SWR 相比,它有点不同,因为 SWR 在底层使用 Fetch API 从服务器请求数据——它是一种建立在它之上的层。 但是,它具有一些不错的功能,例如缓存、分页、滚动位置恢复、依赖获取等,并且准确地说是 Axios 或 Fetch 所不具备的一定程度的开箱即用的反应性。 这是一个很大的优势,因为拥有这些特性有助于使我们的 React 应用程序快速且用户友好,并显着减少我们的代码大小。
最后,请记住,SWR 与 Axios 或 Fetch 不同,即使它有助于处理 HTTP 请求。 SWR 比它们更先进,它提供了一些增强功能以使我们的应用程序与后端保持同步,从而提高我们的应用程序的性能。
现在我们知道 SWR 与 Axios 库或 Fetch API 相比有什么不同,是时候深入研究为什么使用这样的工具了。
推荐阅读:使用 Fetch 和 Axios 在 React 中使用 REST API
为什么使用 SWR 进行数据获取?
正如我之前所说,SWR 附带了一些方便的功能,有助于轻松提高应用程序的可用性。 使用 SWR,您可以使用useSWRPages
对数据进行分页,还可以获取依赖于另一个请求的数据或在返回给定页面时恢复滚动位置,等等。
通常,我们在从服务器获取数据时向用户显示加载消息或微调器。 使用 SWR,您可以在从 API 检索新数据时向用户显示缓存或陈旧数据,从而使其变得更好。 一旦该操作完成,它将重新验证数据以显示新版本。 而且您不需要做任何事情,SWR 将在您第一次获取数据时缓存数据,并在发出新请求时自动检索它。
到目前为止,我们已经明白了为什么使用 SWR 而不是 Axios 或 Fetch 会更好,这显然取决于您的目标是构建什么。 但在很多情况下,我会推荐使用 SWR,因为它不仅具有获取和返回数据的强大功能。
也就是说,我们现在可以开始构建我们的 React 应用程序并使用 SWR 库来获取远程数据。
所以,让我们从建立一个新项目开始。
配置
正如我之前在介绍中所说,我们将构建一个从 Pokemon API 获取数据的应用程序。 如果你愿意,你也可以使用不同的 API,我现在会坚持使用它。
要创建一个新的应用程序,我们需要在终端上运行以下命令:
npx create-react-app react-swr
接下来,我们需要首先导航到包含 React 应用程序的文件夹来安装 SWR 库。
cd react-swr
并在终端上运行以下命令来安装 SWR 包。
yarn add swr
或者,如果您使用的是 npm:
npm install swr
现在我们已经完成了所有设置,让我们如下构建项目以开始使用 SWR:
src ├── components | └── Pokemon.js ├── App.js ├── App.test.js ├── index.js ├── serviceWorker.js ├── setupTests.js ├── package.json ├── README.md ├── yarn-error.log └── yarn.lock
如您所见,文件夹结构很简单。 唯一需要注意的是包含Pokemon.js
文件的components
文件夹。 一旦我们从 API 获取数据,它将在稍后用作演示组件来显示单个 Pokemon。
伟大的! 有了这些,我们现在可以开始使用useSWR
从 API 获取数据。
获取远程数据
正如我们在上面看到的,SWR 包有一些方便的特性。 但是,有两种配置此库的方法:本地或全局。
本地设置意味着每次我们创建一个新文件时,我们都必须再次设置 SWR 才能获取远程数据。 全局设置允许我们在不同文件中重用配置的一部分,因为fetcher
函数可以声明一次并在任何地方使用。
不用担心,我们将在本文中看到两者,但现在,让我们动手并在App.js
文件中添加一些有意义的代码。
显示数据
import React from 'react' import useSWR from 'swr' import { Pokemon } from './components/Pokemon' const url = 'https://pokeapi.co/api/v2/pokemon' const fetcher = (...args) => fetch(...args).then((res) => res.json()) function App() { const { data: result, error } = useSWR(url, fetcher) if (error) return <h1>Something went wrong!</h1> if (!result) return <h1>Loading...</h1> return ( <main className='App'> <h1>Pokedex</h1> <div> {result.results.map((pokemon) => ( <Pokemon key={pokemon.name} pokemon={pokemon} /> ))} </div> </main> ) } export default App
如您所见,我们首先从 SWR 库中导入useSWR
。 这声明了您要从中获取数据的 API 的 URL,以及用于获取这些数据的函数。
这里使用函数fetcher
将数据转换为 JSON。 它接收作为参数获取的数据并返回一些东西。
请注意,在这里,我使用 Rest 运算符( (...args)
),因为我不确定作为参数接收的数据的类型和长度,因此,我复制所有内容,然后再次将其作为参数传递给fetch
useSWR
提供的方法将数据转换为 JSON 并返回。
也就是说,API 的fetcher
和url
现在可以作为参数传递给useSWR
挂钩。 有了它,它现在可以发出请求并返回两种状态:获取的数据和错误状态。 而data: result
与data.result
相同,我们使用对象解构从data
中提取result
。
使用返回的值,我们现在可以检查数据是否成功获取,然后循环遍历它。 并且对于每个用户,使用 Pokemon 组件来显示它。
现在我们有了数据并将其传递给 Pokemon 组件,是时候更新Pokemon.js
以便能够接收和显示数据了。
创建口袋妖怪组件
import React from 'react' import useSWR from 'swr' const fetcher = (...args) => fetch(...args).then((res) => res.json()) export const Pokemon = ({ pokemon }) => { const { name } = pokemon const url = 'https://pokeapi.co/api/v2/pokemon/' + name const { data, error } = useSWR(url, fetcher) if (error) return <h1>Something went wrong!</h1> if (!data) return <h1>Loading...</h1> return ( <div className='Card'> <span className='Card--id'>#{data.id}</span> <img className='Card--image' src={data.sprites.front_default} alt={name} /> <h1 className='Card--name'>{name}</h1> <span className='Card--details'> {data.types.map((poke) => poke.type.name).join(', ')} </span> </div> ) }
在这里,我们有一个组件从 API 接收单个 Pokemon 数据并显示它。 然而,接收到的数据并不包含所有需要的字段,因此我们必须向 API 发出另一个请求以获取完整的 Pokemon 对象。
如您所见,即使这次我们将 Pokemon 的名称附加到 URL,我们也使用相同的过程来检索数据。
顺便说一句,如果你不熟悉解构, ({ pokemon })
与接收 props 并通过props.pokemon
访问 pokemon 对象是一样的。 这只是从对象或数组中提取值的简写。
有了它,如果您导航到项目的根文件夹并在终端上运行以下命令:
yarn start
或者,如果您使用的是 npm:
npm start
您应该会看到数据已成功从 Pokemon API 获取并按预期显示。
伟大的! 我们现在可以使用 SWR 获取远程数据。 但是,此设置是本地设置,可能有点多余,因为您已经可以看到App.js
和Pokemon.js
使用相同的 fetcher 函数来做同样的事情。
但幸运的是,该软件包附带了一个名为SWRConfig
的便捷提供程序,可帮助全局配置 SWR。 它是一个包装器组件,它允许子组件使用全局配置,因此也可以使用 fetcher 函数。
要全局设置 SWR,我们需要更新index.js
文件,因为它是使用 React DOM 渲染 App 组件的地方。 如果需要,可以直接在App.js
文件中使用SWRConfig
。
全局配置 SWR
import React from 'react' import ReactDOM from 'react-dom' import { SWRConfig } from 'swr' import App from './App' import './index.css' const fetcher = (...args) => fetch(...args).then((res) => res.json()) ReactDOM.render( <React.StrictMode> <SWRConfig value={{ fetcher }}> <App /> </SWRConfig> </React.StrictMode>, document.getElementById('root') )
如您所见,我们首先导入SWRConfig
,它是一个提供程序,需要包装更高的组件,或者只是需要使用 SWR 功能的 React 应用程序的一部分。 它将一个期望配置对象的值作为道具。 您可以将多个属性传递给配置对象,这里我只需要获取数据的函数。

现在,我们不再在每个文件中声明fetcher
函数,而是在此处创建它并将其作为值传递给SWRConfig
。 有了这个,我们现在可以在我们的应用程序中检索任何级别的数据,而无需创建另一个函数,从而避免冗余。
除此之外, fetcher
等于fetcher: fetcher
,它只是 ES6 提出的语法糖。 随着这一变化,我们现在需要更新我们的组件以使用全局配置。
使用全局 SWR 配置
import React from 'react' import useSWR from 'swr' import { Pokemon } from './components/Pokemon' const url = 'https://pokeapi.co/api/v2/pokemon' function App() { const { data: result, error } = useSWR(url) if (error) return <h1>Something went wrong!</h1> if (!result) return <h1>Loading...</h1> return ( <main className='App'> <h1>Pokedex</h1> <div> {result.results.map((pokemon) => ( <Pokemon key={pokemon.name} pokemon={pokemon} /> ))} </div> </main> ) } export default App
现在我们只需要将url
传递给useSWR
,而不是传递url
和fetcher
方法。 让我们稍微调整一下 Pokemon 组件。
import React from 'react' import useSWR from 'swr' export const Pokemon = ({ pokemon }) => { const { name } = pokemon const url = 'https://pokeapi.co/api/v2/pokemon/' + name const { data, error } = useSWR(url) if (error) return <h1>Something went wrong!</h1> if (!data) return <h1>Loading...</h1> return ( <div className='Card'> <span className='Card--id'>#{data.id}</span> <img className='Card--image' src={data.sprites.front_default} alt={name} /> <h1 className='Card--name'>{name}</h1> <span className='Card--details'> {data.types.map((poke) => poke.type.name).join(', ')} </span> </div> ) }
您已经可以看到我们不再有 fetcher 函数,这要归功于全局配置将函数传递给useSWR
引擎盖下。
现在,您可以在应用程序的任何地方使用全局 fetcher 功能。 useSWR
挂钩唯一需要获取远程数据的是 URL。
但是,我们仍然可以通过创建自定义钩子来进一步增强设置,以避免一次又一次地声明 URL,而只需将路径作为参数传递。
通过创建自定义挂钩进行高级设置
为此,您必须在项目的根目录中创建一个名为useRequest.js
的新文件(您可以随意命名)并将此代码块添加到它下面。
import useSwr from 'swr' const baseUrl = 'https://pokeapi.co/api/v2' export const useRequest = (path, name) => { if (!path) { throw new Error('Path is required') } const url = name ? baseUrl + path + '/' + name : baseUrl + path const { data, error } = useSwr(url) return { data, error } }
在这里,我们有一个函数,它接收路径和可选的名称,并将其附加到基本 URL 以构建完整的 URL。 接下来,它检查是否接收到名称参数并相应地处理它。
然后,该 URL 作为参数传递给useSWR
挂钩,以便能够获取远程数据并返回它。 如果没有通过路径,则会引发错误。
伟大的! 我们现在需要稍微调整组件以使用我们的自定义钩子。
import React from 'react' import { useRequest } from './useRequest' import './styles.css' import { Pokemon } from './components/Pokemon' function App() { const { data: result, error } = useRequest('/pokemon') if (error) return <h1>Something went wrong!</h1> if (!result) return <h1>Loading...</h1> return ( <main className='App'> <h1>Pokedex</h1> <div> {result.results.map((pokemon) => ( <Pokemon key={pokemon.name} pokemon={pokemon} /> ))} </div> </main> ) } export default App
现在,我们不使用 SWR 钩子,而是使用构建在它之上的自定义钩子,然后按预期将路径作为参数传递。 有了这些,一切都将像以前一样工作,但配置更加简洁和灵活。
让我们也更新 Pokemon 组件。
import React from 'react' import { useRequest } from '../useRequest' export const Pokemon = ({ pokemon }) => { const { name } = pokemon const { data, error } = useRequest('/pokemon', name) if (error) return <h1>Something went wrong!</h1> if (!data) return <h1>Loading...</h1> return ( <div className='Card'> <span className='Card--id'>#{data.id}</span> <img className='Card--image' src={data.sprites.front_default} alt={name} /> <h1 className='Card--name'>{name}</h1> <span className='Card--details'> {data.types.map((poke) => poke.type.name).join(', ')} </span> </div> ) }
您已经可以看到我们的自定义钩子如何使事情变得更容易和更灵活。 在这里,我们只需要将要获取的 Pokemon 的名称额外传递给useRequest
,它就会为我们处理一切。
我希望你开始喜欢这个很酷的库——但是,我们仍然有一些东西要发现,因为 SWR 提供了很多功能,其中之一是useSWRPages
,它是一个轻松对数据进行分页的钩子。 所以,让我们在项目中使用那个钩子。
使用useSWRPages
我们的数据进行分页
SWR 允许我们轻松地对数据进行分页并仅请求其中的一部分,并在需要时重新获取数据以显示下一页。
现在,让我们在项目的根目录中创建一个新文件usePagination.js
并将其用作分页的自定义钩子。
import React from 'react' import useSWR, { useSWRPages } from 'swr' import { Pokemon } from './components/Pokemon' export const usePagination = (path) => { const { pages, isLoadingMore, loadMore, isReachingEnd } = useSWRPages( 'pokemon-page', ({ offset, withSWR }) => { const url = offset || `https://pokeapi.co/api/v2${path}` const { data: result, error } = withSWR(useSWR(url)) if (error) return <h1>Something went wrong!</h1> if (!result) return <h1>Loading...</h1> return result.results.map((pokemon) => ( <Pokemon key={pokemon.name} pokemon={pokemon} /> )) }, (SWR) => SWR.data.next, [] ) return { pages, isLoadingMore, loadMore, isReachingEnd } }
如您所见,这里我们首先导入useSWRPages
,它是允许轻松分页数据的助手。 它接收 4 个参数:请求pokemon-page
的键,它也用于缓存,一个获取数据的函数,如果数据被成功检索,则返回一个组件,另一个函数从SWR
对象和请求数据中获取下一页,以及一系列依赖项。
一旦获取数据,函数useSWRPages
返回几个值,但这里我们需要其中 4 个:作为数据返回的组件的pages
,函数isLoadingMore
检查当前是否获取数据,函数loadMore
帮助获取更多数据,方法isReachingEnd
确定是否还有数据要检索。
现在我们有了返回所需值以对数据进行分页的自定义钩子,我们现在可以移动到App.js
文件并对其进行一些调整。
import React from 'react' import { usePagination } from './usePagination' import './styles.css' export default function App() { const { pages, isLoadingMore, loadMore, isReachingEnd } = usePagination( '/pokemon' ) return ( <main className='App'> <h1>Pokedex</h1> <div>{pages}</div> <button onClick={loadMore} disabled={isLoadingMore || isReachingEnd} > Load more... </button> </main> ) }
导入usePagination
钩子后,我们现在可以将路径作为参数传递并取回返回值。 而且由于pages
是一个组件,我们不需要循环访问数据或类似的东西。
接下来,我们使用按钮上的函数loadMore
来获取更多数据,并在检索操作未完成或没有数据可获取时禁用它。
伟大的! 通过该更改,我们现在可以浏览项目的根目录并使用此命令启动服务器以预览我们的应用程序。
yarn start
或者,如果您使用的是 npm:
npm start
您应该看到数据已成功获取,如果您单击按钮,SWR 将检索新数据。

到目前为止,我们已经在实践中看到了 SWR 库,我希望你能从中找到价值。 但是,它仍然可以提供一些功能。 让我们在下一节深入了解这些功能。
SWR 的其他特点
SWR 库有很多方便的东西,可以简化我们构建 React 应用程序的方式。
焦点重新验证
当您重新聚焦页面或在选项卡之间切换时,该功能允许更新或重新验证准确的数据。 默认情况下,此功能已启用,但如果它不符合您的需要,您仍可以禁用它。 如果您拥有具有高频率更新的数据,它尤其有用。
间隔重新获取
SWR 库允许在一定时间后重新获取数据。 当您的数据高速更改或您需要发出新请求以从数据库中获取一条新信息时,它会很方便。
局部突变
使用 SWR,您可以设置一个临时本地状态,该状态将在获取新数据时自动更新(重新验证)。 当您处理离线优先方法时,此功能尤其有用,它有助于轻松更新数据。
滚动位置恢复
此功能非常方便,尤其是在处理大量列表时。 它允许您在返回页面后恢复滚动位置。 在任何情况下,它都会增加您的应用程序的可用性。
依赖获取
SWR 允许您获取依赖于其他数据的数据。 这意味着它可以获取数据 A,并且一旦该操作完成,它就会使用它来获取数据 B,同时避免瀑布。 当您拥有关系数据时,此功能会有所帮助。
也就是说,SWR 有助于在任何事情上增加用户体验。 它有更多的功能,而且在许多情况下,最好通过 Fetch API 或 Axios 库来使用它。
结论
在整篇文章中,我们看到了为什么 SWR 是一个很棒的库。 它允许使用 React Hooks 进行远程数据获取,并有助于简化一些开箱即用的高级功能,例如分页、缓存数据、间隔重新获取、滚动位置恢复等。 SWR 也与后端无关,这意味着它可以从任何类型的 API 或数据库中获取数据。 确切地说,SWR 极大地提高了 React 应用程序的用户体验,它有一个光明的未来,你应该密切关注它,或者更好地在你的下一个 React 应用程序中使用它。
您可以在此处实时预览完成的项目。
谢谢阅读!
下一步
您可以继续查看以下链接,这将使您在本教程的范围之外有更好的理解。
- 驻波比
- SWR 文档
关于 SmashingMag 的进一步阅读:
- React 中的样式组件
- 使用 Immer 的更好的减速器
- React 中的高阶组件
- 使用 Tailwind 构建可重用的 React 组件