React內(nèi)部實(shí)現(xiàn)cache方法示例詳解
引言
前幾天寫的一篇介紹use
這個(gè)新hook
的文章中聊到React
原生實(shí)現(xiàn)了一個(gè)緩存函數(shù)的方法 —— cache
。
對于如下代碼,被cache
包裹的函數(shù),當(dāng)多次調(diào)用時(shí),如果傳參不變,會(huì)始終返回緩存值:
const cacheFn = cache(fn); cacheFn(1, 2, 3); // 不會(huì)執(zhí)行fn,直接返回緩存值 cacheFn(1, 2, 3);
React
內(nèi)為什么需要cache
方法呢?考慮如下組件:
const fetch = cache(fetchUserData); function User({id}) { const {name} = use(fetch(id)); return <p>{name}</p>; }
User
組件會(huì)根據(jù)用戶id
請求用戶數(shù)據(jù),并渲染用戶名。
如果id
改變,那么fetch
方法重新發(fā)起請求是正常邏輯。
但是,React
組件經(jīng)常render
,如果在id
不變的情況下,由于User
組件render
導(dǎo)致不斷發(fā)起請求,顯然是不合理的。
所以,這種情況下就需要cache
方法。當(dāng)id
不變時(shí),即使User
組件反復(fù)render
,fetch(id)
都返回同一個(gè)值。
本文來聊聊cache
的源碼實(shí)現(xiàn)。
分析實(shí)現(xiàn)思路
整個(gè)方法實(shí)現(xiàn)一共有64行代碼,首先我們來分析下實(shí)現(xiàn)要點(diǎn)。
如果參數(shù)不變,則使用緩存的值。這意味著我們需要處理:
參數(shù)的順序
舉個(gè)例子,當(dāng)參數(shù)順序變了,不使用緩存值:
const cacheFn = cache(fn); cacheFn(1, 2, 3); // 不使用緩存值 cacheFn(3, 2, 1);
區(qū)別處理引用類型、原始類型參數(shù)
舉個(gè)例子,當(dāng)同一位置的參數(shù)傳遞了同一個(gè)引用類型值,則返回緩存值:
const cacheFn = cache(fn); const obj = {}; cacheFn(1, obj, 3); // 返回緩存值 cacheFn(1, obj, 3);
當(dāng)同一位置的參數(shù)傳遞了不同引用類型值,則不返回緩存值:
const cacheFn = cache(fn); const obj = {}; cacheFn(1, obj, 3); // 不返回緩存值 cacheFn(1, {}, 3);
緩存的垃圾回收
緩存數(shù)據(jù)時(shí),要注意緩存失效但是引用的數(shù)據(jù)沒有釋放造成的內(nèi)存泄漏問題。
所以,對于引用類型數(shù)據(jù),可以使用WeakMap
保存。
對于原始類型數(shù)據(jù),可以使用Map
保存。
WeakMap
與Map
的區(qū)別在于 —— 在WeakMap
中,key
到他對應(yīng)的value
是弱引用。這意味著當(dāng)沒有其他數(shù)據(jù)引用這個(gè)key
時(shí),他可以被垃圾回收。而在Map
中,key
到value
是強(qiáng)引用,即使沒有其他數(shù)據(jù)引用這個(gè)key
,他也不會(huì)被垃圾回收。
實(shí)現(xiàn)原理
本文不會(huì)介紹具體的代碼實(shí)現(xiàn)(大段貼代碼讓人看起來頭疼)。
我會(huì)用示例圖講解實(shí)現(xiàn)原理。了解原理后,如果你對實(shí)現(xiàn)細(xì)節(jié)感興趣,可以參考:
對于如下代碼:
const cacheFn = cache(fn); const obj = {}; cacheFn(1, obj, 3);
cacheFn
的每個(gè)傳參,對應(yīng)cache
內(nèi)部的一個(gè)cacheNode
節(jié)點(diǎn):
// CacheNode構(gòu)造函數(shù) function createCacheNode<T>(): CacheNode<T> { return { s: UNTERMINATED, v: undefined, o: null, p: null }; }
字段的意義如下:
- s:
cacheNode
的緩存狀態(tài),有 未中止/中止/發(fā)生錯(cuò)誤 3種狀態(tài) - v:
cacheNode
緩存的值 - o:緩存的引用類型值
- p:緩存的原始類型值
上述cacheFn
執(zhí)行后會(huì)生成如下cacheNode
鏈?zhǔn)浇Y(jié)構(gòu):
讓我們看看這個(gè)鏈?zhǔn)浇Y(jié)構(gòu)如何解決文章開篇提到的3個(gè)問題。
如何解決參數(shù)的順序?
可以看到,上圖中最后一個(gè)cacheNode
節(jié)點(diǎn)的狀態(tài)(cacheNode.s
)為中止。
如果后續(xù)執(zhí)行cacheFn
傳入相同的參數(shù),則會(huì)復(fù)用緩存的cacheNode
節(jié)點(diǎn)。
如果所有傳參都相同,那么會(huì)復(fù)用完整的cacheNode
鏈,此時(shí)最后一個(gè)cacheNode
節(jié)點(diǎn)為中止狀態(tài),則不需要重新執(zhí)行cacheFn
方法計(jì)算返回值,而是直接返回緩存的值(cacheNode.v
)。
如果后續(xù)執(zhí)行cacheFn
,傳入新的參數(shù),則前后的cacheNode
鏈不會(huì)一致。
比如:
// 第一次 cacheFn(1, obj, 3); // 第二次 cacheFn(1, 3, obj);
則第二次生成的cacheNode
鏈中,第二個(gè)節(jié)點(diǎn)就與之前不同(之前obj,之后3),則后續(xù)cacheNode
節(jié)點(diǎn)也不會(huì)相同。
通過這種鏈?zhǔn)浇Y(jié)構(gòu),保證了只有當(dāng)所有參數(shù)保持一致,才能返回緩存的值。否則將重新執(zhí)行函數(shù),并緩存新的返回值與cacheNode
鏈。
如何處理引用類型值
可以從圖中發(fā)現(xiàn),對于引用類型參數(shù)(比如示例中的obj
),對應(yīng)一個(gè)weakMap
節(jié)點(diǎn)。
這不僅意味著當(dāng)沒有其他數(shù)據(jù)引用他時(shí),這個(gè)cacheNode
節(jié)點(diǎn)能夠釋放內(nèi)存,同時(shí)也意味著這個(gè)cacheNode
之后的cacheNode
鏈會(huì)斷掉,他們占用的內(nèi)存也會(huì)釋放。
而原始類型值不存在這樣的問題,從圖中可以發(fā)現(xiàn),原始類型值對應(yīng)一個(gè)map
節(jié)點(diǎn)。
總結(jié)
cache
方法是React
內(nèi)部實(shí)現(xiàn),未來會(huì)暴露給開發(fā)者使用的緩存方法,可以緩存任意函數(shù)。
當(dāng)多次執(zhí)行并傳遞相同的參數(shù)給cache
包裹的函數(shù)時(shí),后續(xù)執(zhí)行會(huì)返回緩存的值。
這是為了應(yīng)對某些函數(shù)需要在React組件多次render間返回穩(wěn)定的值的場景。
比如:對于相同的傳參,請求數(shù)據(jù)的函數(shù)返回同一個(gè)promise
。
cache
的實(shí)現(xiàn)方式是 —— 基于傳參,構(gòu)造一條cacheNode
鏈,傳參的穩(wěn)定對應(yīng)了鏈表的穩(wěn)定,并最終對應(yīng)了返回值的穩(wěn)定。
以上就是React內(nèi)部實(shí)現(xiàn)cache方法示例詳解的詳細(xì)內(nèi)容,更多關(guān)于React內(nèi)部實(shí)現(xiàn)cache方法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
react滾動(dòng)加載useInfiniteScroll?詳解
使用useInfiniteScroll?hook可以處理檢測用戶何時(shí)滾動(dòng)到頁面底部的邏輯,并觸發(fā)回調(diào)函數(shù)以加載更多數(shù)據(jù),它還提供了一種簡單的方法來管理加載和錯(cuò)誤消息的狀態(tài),今天通過實(shí)例代碼介紹下react滾動(dòng)加載useInfiniteScroll?相關(guān)知識,感興趣的朋友跟隨小編一起看看吧2023-09-09React native ListView 增加頂部下拉刷新和底下點(diǎn)擊刷新示例
這篇文章主要介紹了React native ListView 增加頂部下拉刷新和底下點(diǎn)擊刷新示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04React將組件作為參數(shù)進(jìn)行傳遞的3種方法實(shí)例
其實(shí)react組件之間傳遞參數(shù)是比較簡單的,組件傳入?yún)?shù)的一種方式,下面這篇文章主要給大家介紹了關(guān)于React將組件作為參數(shù)進(jìn)行傳遞的3種方法,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07react redux中如何獲取store數(shù)據(jù)并將數(shù)據(jù)渲染出來
這篇文章主要介紹了react redux中如何獲取store數(shù)據(jù)并將數(shù)據(jù)渲染出來,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08ReactNative踩坑之配置調(diào)試端口的解決方法
本篇文章主要介紹了ReactNative踩坑之配置調(diào)試端口的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07React?antd中setFieldsValu的簡便使用示例代碼
form.setFieldsValue是antd?Form組件中的一個(gè)方法,用于動(dòng)態(tài)設(shè)置表單字段的值,它接受一個(gè)對象作為參數(shù),對象的鍵是表單字段的名稱,值是要設(shè)置的字段值,這篇文章主要介紹了React?antd中setFieldsValu的簡便使用,需要的朋友可以參考下2023-08-08基于React實(shí)現(xiàn)無限滾動(dòng)表格
以文本為例,為了實(shí)現(xiàn)無限循環(huán)的視覺效果,我們需要準(zhǔn)備兩段相同的文本,并讓第二段文本的頭部銜接在第一段文本的尾部,同時(shí),為兩段文本設(shè)置相同的滾動(dòng)動(dòng)畫,本文給大家介紹了基于React實(shí)現(xiàn)無限滾動(dòng)表格,需要的朋友可以參考下2023-11-11React 中常用的幾種路由跳轉(zhuǎn)方式小結(jié)
基本路由跳轉(zhuǎn)是最常見的一種方式,下面介紹React 中常用的幾種路由跳轉(zhuǎn)方式,感興趣的朋友一起看看吧2023-12-12