網(wǎng)站首頁 編程語言 正文
引言
免責(zé)聲明:渲染優(yōu)化是所有應(yīng)用的進(jìn)階話題。React Query已經(jīng)進(jìn)行了許多性能優(yōu)化并且開箱即用,大多數(shù)時候不需要做更多優(yōu)化。"不必要的重新渲染"是一個很多人投入大量關(guān)注的話題,也是我要寫這篇文章的原因。但是我要再一次指出,大部分情況下對于大多數(shù)應(yīng)用來說,渲染優(yōu)化很可能并沒有想得那么重要。重新渲染是一個好事情。它保證了你的應(yīng)用展示了最新的狀態(tài)。相比于重復(fù)渲染,我更關(guān)注由于缺少渲染而導(dǎo)致的渲染錯誤。對于更多關(guān)于這個話題的討論,可以看下面的內(nèi)容:
- Fix the slow render before you fix the re-render
- this article by @ryanflorence about premature optimizations
我在第二篇文章介紹select的內(nèi)容中已經(jīng)講了一些關(guān)于渲染優(yōu)化的事情。然而,"為什么在沒有任何數(shù)據(jù)變化的情況下,React Query會渲染兩次組件呢"是我平時被問到最多的一個問題。我們讓我來嘗試深入解釋一下。
isFetching
在之前的例子中我說過,下面這個組件只會在todos的length變化時才會重新渲染,其實我只說了一部分事實:
export const useTodosQuery = (select) => useQuery(['todos'], fetchTodos, { select }) export const useTodosCount = () => useTodosQuery((data) => data.length) function TodosCount() { const todosCount = useTodosCount() return <div>{todosCount.data}</div> }
每次發(fā)生后臺refetch的時候,這個組件都會下面的數(shù)據(jù)分別進(jìn)行一次渲染:
{ status: 'success', data: 2, isFetching: true } { status: 'success', data: 2, isFetching: false }
這是因為React Query在每個查詢中返回了很多基本信息,isFetching
就是其中一個。這個屬性在請求正在發(fā)生的時候會被設(shè)置為true。這個在你想要展示一個后臺請求的loading標(biāo)志的時候特別有用。但是如果你不需要,那確實會造成一些不必要的渲染。
notifiOnChange
對于上面說到的這個場景,React Query提供了notifyOnChangeProps
參數(shù)。他可以在每個場景單獨設(shè)置來告訴React Query:只在這些屬性發(fā)生變化的時候再通知我。通過將這個參數(shù)設(shè)置為['data']
,我們可以實現(xiàn)一個新的版本:
export const useTodosQuery = (select, notifyOnChangeProps) => useQuery(['todos'], fetchTodos, { select, notifyOnChangeProps }) export const useTodosCount = () => useTodosQuery((data) => data.length, ['data'])
保持同步
盡管上面的代碼可以正常工作,但是它很容易就會造成不同步。如果我們希望針對error
進(jìn)行特殊處理呢?又或者我們需要使用isLoading
屬性呢?我們不得不確保notifyOnChangeProps
屬性和我們實際用到的數(shù)據(jù)保持同步。如果我們忘記將某個數(shù)據(jù)添加到屬性里面,而只監(jiān)聽data屬性的變化,當(dāng)查詢返回錯誤,同時我們也要展示這些錯誤的時候,我們的組件并不會重新渲染。這個問題當(dāng)我們把這些屬性寫死在自定義hook的時候格外明顯,因為我們并不知道使用自定義hook的組件實際上會用到哪些數(shù)據(jù):
export const useTodosCount = () => useTodosQuery((data) => data.length, ['data']) function TodosCount() { // ?? we are using error, but we are not getting notified if error changes! const { error, data } = useTodosCount() return ( <div> {error ? error : null} {data ? data : null} </div> ) }
就像我在文章開頭免責(zé)聲明中說的,我認(rèn)為這是比偶爾發(fā)生的不必要的重新渲染更壞的事情。當(dāng)然,我們可以傳參數(shù)給自定義hook,但是這還是需要手動處理,是否有什么方式可以自動處理這個情況呢?請看:
被追蹤的查詢
這是我感受特別自豪的一個特性,這也是我對這個庫第一個重大的貢獻(xiàn)。如果你將notifyOnChangeProps
設(shè)置為'tracked'
,React Query會跟蹤你在渲染過程中用到的數(shù)據(jù),會自動計算依賴列表。最終的效果就跟你手動維護(hù)這個列表一樣,除了你不用再去關(guān)注這個問題以外。你也可以全局開啟這個特性:
const queryClient = new QueryClient({ defaultOptions: { queries: { notifyOnChangeProps: 'tracked', }, }, }) function App() { return ( <QueryClientProvider client={queryClient}> <Example /> </QueryClientProvider> ) }
利用這個特性,你再也不用考慮重新渲染。當(dāng)然這個特性也有一些限制,這就是為什么這個特性是一個可選項:
如果你使用對象剩余屬性結(jié)構(gòu)的語法的話,最終所有屬性都會被追蹤。正常的解構(gòu)語法是沒問題的,不要這么做:
// ?? will track all fields const { isLoading, ...queryInfo } = useQuery(...) // ? this is totally fine const { isLoading, data } = useQuery(...)
被追蹤的查詢只會追蹤render過程中用到的數(shù)據(jù)。如果你只在effects中用到了這些數(shù)據(jù),他們并不會被追蹤。
const queryInfo = useQuery(...) // ?? will not corectly track data React.useEffect(() => { console.log(queryInfo.data) }) // ? fine because the dependency array is accessed during render React.useEffect(() => { console.log(queryInfo.data) }, [queryInfo.data])
被追蹤的查詢不會在每次render的時候被重置,所以只要你使用了一次某個數(shù)據(jù),你就會在整個組件的生命周期內(nèi)追蹤這個數(shù)據(jù):
const queryInfo = useQuery(...) if (someCondition()) { // ?? we will track the data field if someCondition was true in any previous render cycle return <div>{queryInfo.data}</div> }
結(jié)構(gòu)化共享
一個不同的但是并沒那么重要的React Query默認(rèn)開啟的渲染優(yōu)化是結(jié)構(gòu)化共享。這個特性確保數(shù)據(jù)在所有地方是引用唯一的。舉個例子,假設(shè)我們有下面這個數(shù)據(jù)結(jié)構(gòu):
[ { "id": 1, "name": "Learn React", "status": "active" }, { "id": 2, "name": "Learn React Query", "status": "todo" } ]
現(xiàn)在假設(shè)我們將第一個todo轉(zhuǎn)為done,然后進(jìn)行了一次后臺refetch。我們會從后端拿到一個全新的json:
{ "id": 1, "name": "Learn React", "status": "active" }, { "id": 1, "name": "Learn React", "status": "done" }, { "id": 2, "name": "Learn React Query", "status": "todo" } ]
現(xiàn)在React Query會嘗試對比新老狀態(tài),盡可能多的復(fù)用老的狀態(tài)。在上面的例子中,todo數(shù)據(jù)會是一個新的對象,因為我們更新了一個todo。第一個id為1的對象也會是新的對象,但是對于id為2的對象我們會保持跟對應(yīng)的舊數(shù)據(jù)一樣的引用-React Query會將他復(fù)制一份同樣的引用到新的數(shù)據(jù),因為這部分?jǐn)?shù)據(jù)并沒有發(fā)生變化。
這使得使用selector進(jìn)行部分訂閱變得特別友好:
// ? will only re-render if something within todo with id:2 changes // thanks to structural sharing const { data } = useTodo(2)
就像我之前提到的,對于selector來說結(jié)構(gòu)化共享會用到兩次:一次是在queryFn返回的結(jié)果上,另一次是在selector返回的結(jié)果上。在一些場景,特別是數(shù)據(jù)量比較大的場景,結(jié)構(gòu)化共享會成為一個瓶頸。同時它只能使用在JSON可序列化的數(shù)據(jù)上。如果你不需要這個優(yōu)化,你可以通過將`structuralSharing`設(shè)為false來關(guān)閉這個特性。
原文鏈接:https://segmentfault.com/a/1190000042749089
相關(guān)推薦
- 2022-10-27 kotlin?協(xié)程上下文異常處理詳解_Android
- 2022-04-25 Python中的圖形繪制簡單動畫實操_python
- 2022-04-28 在vmware虛擬機(jī)安裝dpdk的詳細(xì)過程_VMware
- 2023-05-20 linux?shell輸出換行簡單實例_linux shell
- 2022-04-23 配置基于域名訪問的網(wǎng)站以及指定用戶可訪問的網(wǎng)站
- 2022-12-09 python反射機(jī)制內(nèi)置函數(shù)及場景構(gòu)造詳解_python
- 2023-06-18 C#?Marshal類基本概念和入門實例講解_C#教程
- 2022-08-20 python深入講解魔術(shù)方法_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 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)程分支