網(wǎng)站首頁 編程語言 正文
引言
歡迎來到“關(guān)于react-query我不得不說的一些事情”的第二章節(jié)。隨著我越來越深入這個庫以及他的社區(qū),我發(fā)現(xiàn)一些人們經(jīng)常會問到的問題。最開始,我計劃在一篇超長的文章里面把這些都講清楚,最終我還是決定將他們拆分成一些有意義的主題。今天第一個主題是一個很普遍但是很重要的事情:數(shù)據(jù)轉(zhuǎn)換。
數(shù)據(jù)轉(zhuǎn)換
我們不得不面對這個問題-大部分的人并沒有使用GraphQL。如果你使用了,那么恭喜你,因為你可以請求到你期望的數(shù)據(jù)格式。
如果你在使用REST風(fēng)格的API,你就必須受限于后端返回的數(shù)據(jù)格式。所以在使用react-query的時候我們應(yīng)該在什么地方通過什么方式來進(jìn)行數(shù)據(jù)轉(zhuǎn)換呢?
答案只有一個:看情況。
下面列舉出四種進(jìn)行數(shù)據(jù)轉(zhuǎn)換的方式,以及他們的優(yōu)缺點:
后端
這是我最喜歡的方式,如果你有決定權(quán)的話。如果后端返回的數(shù)據(jù)結(jié)構(gòu)是你所期望的話,那么你就什么都不用做了。但是在很多場景這并不太現(xiàn)實,比如一些公共的REST API,特別是在企業(yè)級應(yīng)用中。如果你可以讓后端針對每一個具體的場景都有一個對應(yīng)的接口,那么可以返回你期望的數(shù)據(jù)結(jié)構(gòu)。
- 優(yōu)點:
前端什么都不用做 - 缺點:
并不是所有情況下都能做到
查詢函數(shù)中
查詢函數(shù)是你傳給useQuery
的函數(shù)。他會返回一個Promise,最終返回的數(shù)據(jù)會被存在緩存中。但是這并不意味著你只能按照后端給你的數(shù)據(jù)結(jié)構(gòu)來返回數(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ù)就是這樣的。你在其他地方都不會拿到不是大寫的todo名字了。同時你也拿不到數(shù)據(jù)的原始結(jié)構(gòu)了。如果你查看react-query-devtools,你會看到轉(zhuǎn)換之后的結(jié)構(gòu)。如果你查看網(wǎng)絡(luò)請求,你可以看到原始的數(shù)據(jù)結(jié)構(gòu)。這個可能會有點讓人感到困惑,所以不要忘了你在代碼里面處理了數(shù)據(jù)結(jié)構(gòu)。
同時,在這里react-query并不會做什么優(yōu)化。也就是說每一次fetch被執(zhí)行的時候,你的轉(zhuǎn)換邏輯都會被執(zhí)行。如果轉(zhuǎn)換邏輯很復(fù)雜,需要考慮一下其他轉(zhuǎn)換方式。一些公司在前端會有一個公共的API層來抽象數(shù)據(jù)獲取,所以你可能沒辦法在這個抽象層里面做你的數(shù)據(jù)轉(zhuǎn)換。
- 優(yōu)點:
和API調(diào)用綁定在一起,對上層無感知 - 缺點:
在每次數(shù)據(jù)請求的時候都會運(yùn)行
如果你有一個你無法修改的公共的API層,這個方式不太可行 - 其他:
存儲在緩存中的是轉(zhuǎn)換之后的數(shù)據(jù)結(jié)構(gòu),所以你沒辦法拿到原始的數(shù)據(jù)結(jié)構(gòu)
render函數(shù)中
正如第一章節(jié)中介紹的,你可以自定義一個hook,那么你可以很方便的在這個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)換不會在每次數(shù)據(jù)查詢的時候運(yùn)行,但是會在每次render的時候運(yùn)行(即使這次render并沒有觸發(fā)數(shù)據(jù)請求)。這看起來這不是什么大問題,如果你在意的話,你可以通過useMemo
來進(jìn)行優(yōu)化,同時盡可能只定義真正需要的依賴列表。queryInfo中的data
是引用穩(wěn)定的除非數(shù)據(jù)真的發(fā)生了變化,但是queryInfo
就不是了。如果你把queryInfo
作為你的依賴,那么轉(zhuǎn)換邏輯就會在每次render的時候運(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中有一些額外的邏輯來協(xié)助進(jìn)行數(shù)據(jù)轉(zhuǎn)換的時候,這是一個很好的選擇。需要注意的是data有可能是undefined,所以請使用可選鏈?zhǔn)皆L問來獲取data中的數(shù)據(jù)。
- 優(yōu)點:
可以通過useMemo進(jìn)行優(yōu)化 - 缺點
寫法有一些晦澀
data可能會是undefined - 其他
確切的數(shù)據(jù)結(jié)構(gòu)無法在devtool中展示
使用select配置
v3引入了內(nèi)置的selector,可以用它來進(jìn)行數(shù)據(jù)轉(zhuǎn)換:
export const useTodosQuery = () => useQuery(['todos'], fetchTodos, { select: (data) => data.map((todo) => todo.name.toUpperCase()), })
selector只會在data存在的時候被調(diào)用,所以你不用擔(dān)心undefiend的問題。像上面的selector會在每次render的時候被執(zhí)行,因為函數(shù)表達(dá)式變化了(因為這是一個內(nèi)聯(lián)函數(shù))。如果轉(zhuǎn)換邏輯比較復(fù)雜,你可以使用useCallback來進(jìn)行memoize,或者把他抽象到一個穩(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()), [] ), })
在未來,select配置也可以被用來訂閱data中的部分?jǐn)?shù)據(jù)。這使得這一數(shù)據(jù)轉(zhuǎn)換實現(xiàn)方式變得特別。看看下面這個例子:
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)建了一個像useSelector一樣的API,你可以傳自定義selector到useTodosQuery中。這個自定義hook仍然可以像之前一樣工作,如果你沒有傳select,會返回整個數(shù)據(jù)。
但是如果你傳了selector,你就只會訂閱selector返回的部分?jǐn)?shù)據(jù)。這是很有用的,因為這意味著如果我們更新了一個todo的名字,只通過useTodosCount訂閱了count的組件并不會重新渲染。count沒有發(fā)生變化,所以react-query可以選擇不通知這部分?jǐn)?shù)據(jù)的訂閱者(注意這里說得很容易,但是具體實現(xiàn)不完全跟這個描述一樣,我會在第三部分渲染優(yōu)化中聊一聊這部分內(nèi)容)
- 優(yōu)點:
最佳優(yōu)化
支持部分訂閱 - 其他:
每個訂閱者的數(shù)據(jù)可能都不一樣
原文鏈接:https://segmentfault.com/a/1190000042669711
相關(guān)推薦
- 2024-03-07 MyBatis動態(tài)語句
- 2022-02-15 H5移動端大轉(zhuǎn)盤抽獎插件, 簡單、易用、無依賴
- 2022-06-29 python人工智能tensorflow構(gòu)建循環(huán)神經(jīng)網(wǎng)絡(luò)RNN_python
- 2022-11-14 elasticsearch 性能優(yōu)化
- 2023-10-13 Error: cannot push because a reference that you ar
- 2022-08-10 C#對文件名智能排序的算法_C#教程
- 2022-05-24 Django框架cookie和session方法及參數(shù)設(shè)置_python
- 2022-12-22 一文學(xué)會c語言結(jié)構(gòu)體的定義和使用方法_C 語言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支