日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達(dá)者為師

網(wǎng)站首頁 編程語言 正文

ReactQuery系列React?Query?實(shí)踐示例詳解_React

作者:lakb248 ? 更新時(shí)間: 2022-12-09 編程語言

引言

當(dāng)2018年GraphQL特別是Apolllo Client開始流行之后,很多人開始認(rèn)為它將替代Redux,關(guān)于Redux是否已經(jīng)落伍的問題經(jīng)常被問到。

我很清晰地記得我當(dāng)時(shí)對(duì)這些觀點(diǎn)的不理解。為什么一些數(shù)據(jù)請(qǐng)求的庫會(huì)替代全局狀態(tài)管理庫呢?這兩者有什么關(guān)聯(lián)呢?

曾經(jīng)我認(rèn)為像Apollo這樣的Graphql客戶端只能用來請(qǐng)求數(shù)據(jù),就像axios一樣,你仍然需要一些方式來讓請(qǐng)求的數(shù)據(jù)可以被應(yīng)用程序訪問到。

我發(fā)現(xiàn)我大錯(cuò)特錯(cuò)。

客戶端狀態(tài) vs 服務(wù)端狀態(tài)

Apollo提供的不僅僅是描述所需數(shù)據(jù)同時(shí)獲取數(shù)據(jù)的能力,它同時(shí)提供了針對(duì)這些服務(wù)端數(shù)據(jù)的緩存能力。這意味著你可以在多個(gè)組件中使用相同的useQueryhook,它只會(huì)觸發(fā)一次數(shù)據(jù)請(qǐng)求并且按照請(qǐng)求的先后順序返回緩存中的數(shù)據(jù)。

這看起來跟我們(包括很多除了我們以外的團(tuán)隊(duì))在一些場(chǎng)景使用redux的目的很相似:從服務(wù)器獲取數(shù)據(jù),然后讓這部分?jǐn)?shù)據(jù)可以在所有地方可以被訪問到。

所以似乎我們經(jīng)常將這些服務(wù)端數(shù)據(jù)當(dāng)成客戶端狀態(tài)來看待,除了這些服務(wù)端數(shù)據(jù)(比如:一個(gè)文章列表,你需要顯示的某個(gè)用戶的詳細(xì)信息,...),你的應(yīng)用并不真正擁有它。我們只是借用了最新版本的一份數(shù)據(jù)然后展示給用戶。服務(wù)端才真正擁有這部分?jǐn)?shù)據(jù)。

對(duì)于我來說,這給了我一個(gè)如何看待數(shù)據(jù)的新的思路。如果我們能利用緩存來顯示我們不擁有的那部分?jǐn)?shù)據(jù),那么剩下的應(yīng)用需要處理的真正的客戶端狀態(tài)將大大減少。這使我理解了為什么很多人認(rèn)為Apollo可以在很多場(chǎng)景替代redux。

React Query

我一直沒有機(jī)會(huì)使用GraphQL。我們有現(xiàn)成的REST API,并沒有遇到冗余請(qǐng)求的問題,目前完全溝通。并沒有足夠的理由讓我們轉(zhuǎn)換到GraphQL,特別是你還需要讓后端服務(wù)配合進(jìn)行改動(dòng)。
但是我還是羨慕GraphQL帶來的前端數(shù)據(jù)請(qǐng)求(包括loading和錯(cuò)誤態(tài)的處理)的簡(jiǎn)潔。如果React生態(tài)中有相似的針對(duì)REST API的方案就好了。

讓我們來看看React Query吧。

由Tanner Linsley在2019年開發(fā)的React Query使得在REST API中也可以使用到Apollo所帶來的好處。他支持任何返回Promise的函數(shù)并且使用了stale-while-revalidate緩存策略。庫本身有一些默認(rèn)行為可以盡可能保證數(shù)據(jù)的實(shí)時(shí)性,同時(shí)盡可能快的將數(shù)據(jù)返回給用戶,讓人們感覺近乎實(shí)時(shí)的體驗(yàn)以提供優(yōu)秀的用戶體驗(yàn)。在這之上,他同時(shí)提供了靈活的自定義能力來滿足各種場(chǎng)景。

這篇文章并不會(huì)對(duì)React Query進(jìn)行詳細(xì)的介紹。

我認(rèn)為官方文檔已經(jīng)對(duì)使用和概念進(jìn)行了很好的介紹,同時(shí)也有很多關(guān)于這方面的視頻,并且Tanner開了一門課程可以讓你熟悉這個(gè)庫。

我將會(huì)更多的關(guān)注在官方文檔之外的一些實(shí)踐上的介紹,當(dāng)你已經(jīng)使用這個(gè)庫一段時(shí)間之后,也許這些介紹對(duì)你會(huì)有所幫助。這其中有一些我過去幾個(gè)月在深度使用React Query以及從React Query社區(qū)中總結(jié)出的經(jīng)驗(yàn)。

關(guān)于默認(rèn)行為的解釋

我相信React Query的默認(rèn)行為是經(jīng)過深思熟慮的,但是他們有時(shí)會(huì)讓你措手不及,特別是剛開始使用的時(shí)候。

首先,React Query并不會(huì)在每次render的時(shí)候都執(zhí)行queryFn,即使默認(rèn)的staleTime是0。你的應(yīng)用在任何時(shí)候可能會(huì)因?yàn)楦鞣N原因重新render,所以如果每次都fetch是瘋狂的!
如果你看到了一個(gè)你不希望的refetch,這很可能是因?yàn)槟銊偩劢沽水?dāng)前窗口同時(shí)React Query執(zhí)行了refetchOnWindowFocus,這在生產(chǎn)環(huán)境是一個(gè)很棒的特性:如果用戶在不同的瀏覽器tab之間切換,然后回到了你的應(yīng)用,一個(gè)后臺(tái)的refetch會(huì)被自動(dòng)觸發(fā),如果在同一個(gè)時(shí)間服務(wù)端數(shù)據(jù)發(fā)生了變更,那屏幕上的數(shù)據(jù)會(huì)被更新。所有這些會(huì)在看不到loading態(tài)的情況下發(fā)生,如果數(shù)據(jù)和緩存中的數(shù)據(jù)對(duì)比沒有變化的話,你的組件不會(huì)進(jìn)行重新render。

在開發(fā)階段,這個(gè)現(xiàn)象會(huì)出現(xiàn)得更加頻繁,特別是當(dāng)你在瀏覽器DevTools和你的應(yīng)用之間切換的時(shí)候。

其次,cacheTimestaleTime的區(qū)別似乎經(jīng)常讓人感到困惑,所以讓我來說明一下:

  • StaleTime:一個(gè)查詢變成失效之前的時(shí)長(zhǎng)。如果查詢是有效的,那么查詢就會(huì)一直使用緩存中的數(shù)據(jù),不會(huì)進(jìn)行網(wǎng)絡(luò)請(qǐng)求。如果查詢是處于失效狀態(tài)(默認(rèn)情況下查詢會(huì)立即失效),首先仍然會(huì)從緩存中獲取數(shù)據(jù),但是同時(shí)后臺(tái)在滿足一定條件的情況下會(huì)發(fā)起一次查詢請(qǐng)求。
  • CacheTime:查詢從變成非激活態(tài)到從緩存中移除持續(xù)的時(shí)長(zhǎng)。默認(rèn)是五分鐘,當(dāng)沒有注冊(cè)的觀察者的時(shí)候,查詢會(huì)變成非激活態(tài),所以如果所有使用了某個(gè)查詢的組件都銷毀的時(shí)候,這個(gè)查詢就變成了非激活態(tài)。
    大多數(shù)情況下,如果你要改變這兩個(gè)設(shè)置其中的某一個(gè)的話,大部分情況下應(yīng)該修改staleTime。我很少會(huì)需要修改cacheTime。在文檔里面也有一個(gè)關(guān)于這個(gè)的解釋。

使用React Query DevTools

DevTools會(huì)幫助你更好的理解查詢中的狀態(tài)。它會(huì)告訴你當(dāng)前緩存中的數(shù)據(jù)是什么,所以你可以更方便的進(jìn)行調(diào)試。除了這些,我發(fā)現(xiàn)在DevTools中可以模擬你的網(wǎng)絡(luò)環(huán)境來更直觀的看到后臺(tái)refetch,因?yàn)楸镜胤?wù)一般都很快。

把query key理解成一個(gè)依賴列表

我這里所說的依賴列表是類比useEffect中說到的依賴列表,我假設(shè)你已經(jīng)對(duì)useEffect已經(jīng)比較熟悉了。

為什么這兩者會(huì)是相似的呢?

因?yàn)镽eact Query會(huì)觸發(fā)refetch當(dāng)query key發(fā)生變化。所以當(dāng)我們給queryFn傳了一個(gè)變量的時(shí)候,大部分情況下我們都是希望當(dāng)這個(gè)變量發(fā)生變化的時(shí)候可以請(qǐng)求數(shù)據(jù)。相比于通過復(fù)雜的代碼邏輯來手動(dòng)觸發(fā)一個(gè)refetch,我們可以利用query key:

type State = 'all' | 'open' | 'done'
type Todo = {
  id: number
  state: State
}
type Todos = ReadonlyArray<Todo>
const fetchTodos = async (state: State): Promise<Todos> => {
  const response = await axios.get(`todos/${state}`)
  return response.data
}
export const useTodosQuery = (state: State) =>
  useQuery(['todos', state], () => fetchTodos(state))

這里,想象我們的UI顯示了一個(gè)帶有過濾器的todo列表。我們會(huì)有一些本地狀態(tài)來存儲(chǔ)過濾器的數(shù)據(jù),當(dāng)用戶改變了過濾條件之后,我們會(huì)更新本地的狀態(tài),然后React Query會(huì)自動(dòng)觸發(fā)一個(gè)refetch,因?yàn)閝uery key發(fā)生了變化。我們最終實(shí)現(xiàn)了過濾狀態(tài)和查詢函數(shù)的同步,這與useEffect中的依賴列表很相似。我從來沒有沒有出現(xiàn)過給queryFn傳了一個(gè)變量,但是這個(gè)變量不是queryKey的一部分的情況。

一個(gè)新的緩存入口

因?yàn)閝uery key被用作緩存的key,所以當(dāng)你把狀態(tài)從all改成done的時(shí)候,你會(huì)得到一個(gè)新的緩存入口,當(dāng)你第一次切換過濾狀態(tài)的時(shí)候,會(huì)導(dǎo)致一個(gè)強(qiáng)制的loading狀態(tài)(很可能會(huì)限制一個(gè)loading動(dòng)畫)。這當(dāng)然不是最理想的,所以你可以使用keepPreviousData來處理這種情況,或者你可以使用initialData來為新的緩存入口預(yù)填充數(shù)據(jù)。上面那個(gè)例子可以很完美的解釋這個(gè)情況,因?yàn)槲覀兛梢宰鲆恍┛蛻舳说臄?shù)據(jù)預(yù)過濾:

type State = 'all' | 'open' | 'done'
type Todo = {
  id: number
  state: State
}
type Todos = ReadonlyArray<Todo>
const fetchTodos = async (state: State): Promise<Todos> => {
  const response = await axios.get(`todos/${state}`)
  return response.data
}
export const useTodosQuery = (state: State) =>
  useQuery(['todos', state], () => fetchTodos(state), {
    initialData: () => {
      const allTodos = queryClient.getQueryData<Todos>(['todos', 'all'])
      const filteredData =
        allTodos?.filter((todo) => todo.state === state) ?? []
      return filteredData.length > 0 ? filteredData : undefined
    },
  })

現(xiàn)在,每次用戶切換過濾條件的時(shí)候,如果我們沒有數(shù)據(jù),我們會(huì)嘗試用'all todos'緩存中的數(shù)據(jù)來預(yù)填充。我們可以馬上就顯示'done'的todo給用戶,他們可以在后臺(tái)fetch結(jié)束之后看到更新之后的列表。注意v3版本中,你需要設(shè)置initialStale屬性來觸發(fā)一個(gè)后臺(tái)fetch。
我認(rèn)為這簡(jiǎn)單的幾行代碼可以給你帶來很好的用戶體驗(yàn)的提升。

把服務(wù)端狀態(tài)和客戶端狀態(tài)分開

這個(gè)觀點(diǎn)和我上個(gè)月寫的文檔一樣:如果你從useQuery中拿到了數(shù)據(jù),不要把這部分?jǐn)?shù)據(jù)放到本地狀態(tài)中。主要的原因是這樣會(huì)使得React Query所有后臺(tái)更新失效,因?yàn)閺?fù)制出來的本地狀態(tài)不會(huì)自動(dòng)更新。
如果你希望獲取一些默認(rèn)數(shù)據(jù)來設(shè)置一個(gè)表單的默認(rèn)值,然后使用數(shù)據(jù)來渲染表單,那是可以的。后臺(tái)更新并不會(huì)因?yàn)楸韱我呀?jīng)初始化就忽略之后更新的數(shù)據(jù)。所以如果你想打到這個(gè)目的,確保通過設(shè)置staleTime來避免觸發(fā)不必要的后臺(tái)refetch:

const App = () => {
  const { data } = useQuery('key', queryFn, { staleTime: Infinity })
  return data ? <MyForm initialData={data} /> : null
}
const MyForm = ({ initialData} ) => {
  const [data, setData] = React.useState(initialData)
  ...
}

enabled屬性是很強(qiáng)大的

useQuery?hook有很多屬性可以用來自定義他的行為,enabled屬性是很強(qiáng)大的一個(gè),它可以讓你做很多有意思的事情。下面是一些我們可以利用它來實(shí)現(xiàn)的功能:

  • 依賴查詢

在一個(gè)查詢中獲取數(shù)據(jù),然后第二個(gè)查詢只有當(dāng)我們成功的從上一個(gè)查詢中獲取數(shù)據(jù)的時(shí)候才會(huì)觸發(fā)

  • 開啟/關(guān)閉查詢

假設(shè)我們有一個(gè)定時(shí)查詢,通過refetchInterval來實(shí)現(xiàn),但是當(dāng)一個(gè)彈窗打開的時(shí)候我們可以暫停這個(gè)查詢,避免彈窗后面的內(nèi)容發(fā)生變更。

  • 等待用戶輸入

比如我們有一些過濾條件作為query key,但是當(dāng)用戶還沒進(jìn)行過濾操作的時(shí)候可以不進(jìn)行查詢。

不要把queryCache當(dāng)成本地狀態(tài)管理器

如果你要修改queryCache,它應(yīng)該只發(fā)生在樂觀更新或者在變更之后拿到后臺(tái)返回的新數(shù)據(jù)的時(shí)候。記住任何一個(gè)后臺(tái)refetch都會(huì)覆蓋這些數(shù)據(jù),所以可以使用其他本地狀態(tài)管理庫

創(chuàng)建自定義hook

即使你只是封裝一個(gè)useQuery調(diào)用,創(chuàng)建一個(gè)自定義hook通常情況下也是值得的,因?yàn)椋?/p>

  • 你可以把真實(shí)的數(shù)據(jù)獲取邏輯和UI分離,當(dāng)時(shí)把它和useQuery調(diào)用封裝在一起
  • 你可以把對(duì)于某個(gè)query key的使用都放在同一個(gè)文件里面
  • 如果你需要修改一些設(shè)置或者增加一些數(shù)據(jù)轉(zhuǎn)換邏輯,你可以在一個(gè)地方進(jìn)行
    在上面的todo例子里面已經(jīng)有一些使用場(chǎng)景。

原文鏈接:https://segmentfault.com/a/1190000042629569

欄目分類
最近更新