SWR 簡介:用於遠程數據獲取的 React Hooks

已發表: 2022-03-10
快速總結↬在本文中,我們將研究一種在 React 應用程序中檢索數據的新方法,名為 SWR。 這是一組用於遠程數據獲取的鉤子,使事情變得更容易,例如緩存、分頁等。 我們還將從頭開始構建一個 Pokedex 應用程序,並使用 SWR 功能來獲取數據並對其進行分頁。

SWR 是由 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 的fetcherurl現在可以作為參數傳遞給useSWR掛鉤。 有了它,它現在可以發出請求並返回兩種狀態:獲取的數據和錯誤狀態。 而data: resultdata.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.jsPokemon.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 ,而不是傳遞urlfetcher方法。 讓我們稍微調整一下 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 組件