ReactQuery系列之?dāng)?shù)據(jù)轉(zhuǎn)換示例詳解
引言
歡迎來(lái)到“關(guān)于react-query我不得不說(shuō)的一些事情”的第二章節(jié)。隨著我越來(lái)越深入這個(gè)庫(kù)以及他的社區(qū),我發(fā)現(xiàn)一些人們經(jīng)常會(huì)問(wèn)到的問(wèn)題。最開(kāi)始,我計(jì)劃在一篇超長(zhǎng)的文章里面把這些都講清楚,最終我還是決定將他們拆分成一些有意義的主題。今天第一個(gè)主題是一個(gè)很普遍但是很重要的事情:數(shù)據(jù)轉(zhuǎn)換。
數(shù)據(jù)轉(zhuǎn)換
我們不得不面對(duì)這個(gè)問(wèn)題-大部分的人并沒(méi)有使用GraphQL。如果你使用了,那么恭喜你,因?yàn)槟憧梢哉?qǐng)求到你期望的數(shù)據(jù)格式。
如果你在使用REST風(fēng)格的API,你就必須受限于后端返回的數(shù)據(jù)格式。所以在使用react-query的時(shí)候我們應(yīng)該在什么地方通過(guò)什么方式來(lái)進(jìn)行數(shù)據(jù)轉(zhuǎn)換呢?
答案只有一個(gè):看情況。
下面列舉出四種進(jìn)行數(shù)據(jù)轉(zhuǎn)換的方式,以及他們的優(yōu)缺點(diǎn):
后端
這是我最喜歡的方式,如果你有決定權(quán)的話(huà)。如果后端返回的數(shù)據(jù)結(jié)構(gòu)是你所期望的話(huà),那么你就什么都不用做了。但是在很多場(chǎng)景這并不太現(xiàn)實(shí),比如一些公共的REST API,特別是在企業(yè)級(jí)應(yīng)用中。如果你可以讓后端針對(duì)每一個(gè)具體的場(chǎng)景都有一個(gè)對(duì)應(yīng)的接口,那么可以返回你期望的數(shù)據(jù)結(jié)構(gòu)。
- 優(yōu)點(diǎn):
前端什么都不用做 - 缺點(diǎn):
并不是所有情況下都能做到
查詢(xún)函數(shù)中
查詢(xún)函數(shù)是你傳給useQuery
的函數(shù)。他會(huì)返回一個(gè)Promise,最終返回的數(shù)據(jù)會(huì)被存在緩存中。但是這并不意味著你只能按照后端給你的數(shù)據(jù)結(jié)構(gòu)來(lái)返回?cái)?shù)據(jù)。你可以在返回之前進(jìn)行數(shù)據(jù)轉(zhuǎn)換:
const fetchTodos = async (): Promise<Todos> => { const response = await axios.get('todos') const data: Todos = response.data return data.map((todo) => todo.name.toUpperCase()) } export const useTodosQuery = () => useQuery(['todos'], fetchTodos)
之后你就可以在其他地方使用轉(zhuǎn)換之后的數(shù)據(jù),仿佛后端返回的數(shù)據(jù)就是這樣的。你在其他地方都不會(huì)拿到不是大寫(xiě)的todo名字了。同時(shí)你也拿不到數(shù)據(jù)的原始結(jié)構(gòu)了。如果你查看react-query-devtools,你會(huì)看到轉(zhuǎn)換之后的結(jié)構(gòu)。如果你查看網(wǎng)絡(luò)請(qǐng)求,你可以看到原始的數(shù)據(jù)結(jié)構(gòu)。這個(gè)可能會(huì)有點(diǎn)讓人感到困惑,所以不要忘了你在代碼里面處理了數(shù)據(jù)結(jié)構(gòu)。
同時(shí),在這里react-query并不會(huì)做什么優(yōu)化。也就是說(shuō)每一次fetch被執(zhí)行的時(shí)候,你的轉(zhuǎn)換邏輯都會(huì)被執(zhí)行。如果轉(zhuǎn)換邏輯很復(fù)雜,需要考慮一下其他轉(zhuǎn)換方式。一些公司在前端會(huì)有一個(gè)公共的API層來(lái)抽象數(shù)據(jù)獲取,所以你可能沒(méi)辦法在這個(gè)抽象層里面做你的數(shù)據(jù)轉(zhuǎn)換。
- 優(yōu)點(diǎn):
和API調(diào)用綁定在一起,對(duì)上層無(wú)感知 - 缺點(diǎn):
在每次數(shù)據(jù)請(qǐng)求的時(shí)候都會(huì)運(yùn)行
如果你有一個(gè)你無(wú)法修改的公共的API層,這個(gè)方式不太可行 - 其他:
存儲(chǔ)在緩存中的是轉(zhuǎn)換之后的數(shù)據(jù)結(jié)構(gòu),所以你沒(méi)辦法拿到原始的數(shù)據(jù)結(jié)構(gòu)
render函數(shù)中
正如第一章節(jié)中介紹的,你可以自定義一個(gè)hook,那么你可以很方便的在這個(gè)hook里做數(shù)據(jù)轉(zhuǎn)換:
const fetchTodos = async (): Promise<Todos> => { const response = await axios.get('todos') return response.data } export const useTodosQuery = () => { const queryInfo = useQuery(['todos'], fetchTodos) return { ...queryInfo, data: queryInfo.data?.map((todo) => todo.name.toUpperCase()), } }
正如代碼邏輯所示,數(shù)據(jù)轉(zhuǎn)換不會(huì)在每次數(shù)據(jù)查詢(xún)的時(shí)候運(yùn)行,但是會(huì)在每次render的時(shí)候運(yùn)行(即使這次render并沒(méi)有觸發(fā)數(shù)據(jù)請(qǐng)求)。這看起來(lái)這不是什么大問(wèn)題,如果你在意的話(huà),你可以通過(guò)useMemo
來(lái)進(jìn)行優(yōu)化,同時(shí)盡可能只定義真正需要的依賴(lài)列表。queryInfo中的data
是引用穩(wěn)定的除非數(shù)據(jù)真的發(fā)生了變化,但是queryInfo
就不是了。如果你把queryInfo
作為你的依賴(lài),那么轉(zhuǎn)換邏輯就會(huì)在每次render的時(shí)候運(yùn)行:
export const useTodosQuery = () => { const queryInfo = useQuery(['todos'], fetchTodos) return { ...queryInfo, // ?? don't do this - the useMemo does nothing at all here! data: React.useMemo( () => queryInfo.data?.map((todo) => todo.name.toUpperCase()), [queryInfo] ), // ? correctly memoizes by queryInfo.data data: React.useMemo( () => queryInfo.data?.map((todo) => todo.name.toUpperCase()), [queryInfo.data] ), } }
特別是當(dāng)你在自定義hook中有一些額外的邏輯來(lái)協(xié)助進(jìn)行數(shù)據(jù)轉(zhuǎn)換的時(shí)候,這是一個(gè)很好的選擇。需要注意的是data有可能是undefined,所以請(qǐng)使用可選鏈?zhǔn)皆L(fǎng)問(wèn)來(lái)獲取data中的數(shù)據(jù)。
- 優(yōu)點(diǎn):
可以通過(guò)useMemo進(jìn)行優(yōu)化 - 缺點(diǎn)
寫(xiě)法有一些晦澀
data可能會(huì)是undefined - 其他
確切的數(shù)據(jù)結(jié)構(gòu)無(wú)法在devtool中展示
使用select配置
v3引入了內(nèi)置的selector,可以用它來(lái)進(jìn)行數(shù)據(jù)轉(zhuǎn)換:
export const useTodosQuery = () => useQuery(['todos'], fetchTodos, { select: (data) => data.map((todo) => todo.name.toUpperCase()), })
selector只會(huì)在data存在的時(shí)候被調(diào)用,所以你不用擔(dān)心undefiend的問(wèn)題。像上面的selector會(huì)在每次render的時(shí)候被執(zhí)行,因?yàn)楹瘮?shù)表達(dá)式變化了(因?yàn)檫@是一個(gè)內(nèi)聯(lián)函數(shù))。如果轉(zhuǎn)換邏輯比較復(fù)雜,你可以使用useCallback來(lái)進(jìn)行memoize,或者把他抽象到一個(gè)穩(wěn)定的函數(shù)引用中:
const transformTodoNames = (data: Todos) => data.map((todo) => todo.name.toUpperCase()) export const useTodosQuery = () => useQuery(['todos'], fetchTodos, { // ? uses a stable function reference select: transformTodoNames, }) export const useTodosQuery = () => useQuery(['todos'], fetchTodos, { // ? memoizes with useCallback select: React.useCallback( (data: Todos) => data.map((todo) => todo.name.toUpperCase()), [] ), })
在未來(lái),select配置也可以被用來(lái)訂閱data中的部分?jǐn)?shù)據(jù)。這使得這一數(shù)據(jù)轉(zhuǎn)換實(shí)現(xiàn)方式變得特別??纯聪旅孢@個(gè)例子:
export const useTodosQuery = (select) => useQuery(['todos'], fetchTodos, { select }) export const useTodosCount = () => useTodosQuery((data) => data.length) export const useTodo = (id) => useTodosQuery((data) => data.find((todo) => todo.id === id))
這里,我們創(chuàng)建了一個(gè)像useSelector一樣的API,你可以傳自定義selector到useTodosQuery中。這個(gè)自定義hook仍然可以像之前一樣工作,如果你沒(méi)有傳select,會(huì)返回整個(gè)數(shù)據(jù)。
但是如果你傳了selector,你就只會(huì)訂閱selector返回的部分?jǐn)?shù)據(jù)。這是很有用的,因?yàn)檫@意味著如果我們更新了一個(gè)todo的名字,只通過(guò)useTodosCount訂閱了count的組件并不會(huì)重新渲染。count沒(méi)有發(fā)生變化,所以react-query可以選擇不通知這部分?jǐn)?shù)據(jù)的訂閱者(注意這里說(shuō)得很容易,但是具體實(shí)現(xiàn)不完全跟這個(gè)描述一樣,我會(huì)在第三部分渲染優(yōu)化中聊一聊這部分內(nèi)容)
- 優(yōu)點(diǎn):
最佳優(yōu)化
支持部分訂閱 - 其他:
每個(gè)訂閱者的數(shù)據(jù)可能都不一樣
以上就是ReactQuery系列之?dāng)?shù)據(jù)轉(zhuǎn)換示例詳解的詳細(xì)內(nèi)容,更多關(guān)于ReactQuery 數(shù)據(jù)轉(zhuǎn)換的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解vant2 自動(dòng)檢查表單驗(yàn)證 -validate
這篇文章主要介紹了vant2 自動(dòng)檢查表單驗(yàn)證 -validate,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10React Native驗(yàn)證碼倒計(jì)時(shí)工具類(lèi)分享
這篇文章主要為大家分享了React Native驗(yàn)證碼倒計(jì)時(shí)工具類(lèi),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10詳解在React.js中使用PureComponent的重要性和使用方式
這篇文章主要介紹了詳解在React.js中使用PureComponent的重要性和使用方式,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07React中引入less、less-loader問(wèn)題
這篇文章主要介紹了React中引入less、less-loader問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01關(guān)于useEffect的第二個(gè)參數(shù)解讀
這篇文章主要介紹了關(guān)于useEffect的第二個(gè)參數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09react axios配置代理(proxy),如何解決本地開(kāi)發(fā)時(shí)的跨域問(wèn)題
這篇文章主要介紹了react axios配置代理(proxy),如何解決本地開(kāi)發(fā)時(shí)的跨域問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07