React內(nèi)部實現(xiàn)cache方法示例詳解
引言
前幾天寫的一篇介紹use這個新hook的文章中聊到React原生實現(xiàn)了一個緩存函數(shù)的方法 —— cache。
對于如下代碼,被cache包裹的函數(shù),當多次調(diào)用時,如果傳參不變,會始終返回緩存值:
const cacheFn = cache(fn); cacheFn(1, 2, 3); // 不會執(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組件會根據(jù)用戶id請求用戶數(shù)據(jù),并渲染用戶名。
如果id改變,那么fetch方法重新發(fā)起請求是正常邏輯。
但是,React組件經(jīng)常render,如果在id不變的情況下,由于User組件render導致不斷發(fā)起請求,顯然是不合理的。
所以,這種情況下就需要cache方法。當id不變時,即使User組件反復render,fetch(id)都返回同一個值。
本文來聊聊cache的源碼實現(xiàn)。
分析實現(xiàn)思路
整個方法實現(xiàn)一共有64行代碼,首先我們來分析下實現(xiàn)要點。
如果參數(shù)不變,則使用緩存的值。這意味著我們需要處理:
參數(shù)的順序
舉個例子,當參數(shù)順序變了,不使用緩存值:
const cacheFn = cache(fn); cacheFn(1, 2, 3); // 不使用緩存值 cacheFn(3, 2, 1);
區(qū)別處理引用類型、原始類型參數(shù)
舉個例子,當同一位置的參數(shù)傳遞了同一個引用類型值,則返回緩存值:
const cacheFn = cache(fn);
const obj = {};
cacheFn(1, obj, 3);
// 返回緩存值
cacheFn(1, obj, 3);當同一位置的參數(shù)傳遞了不同引用類型值,則不返回緩存值:
const cacheFn = cache(fn);
const obj = {};
cacheFn(1, obj, 3);
// 不返回緩存值
cacheFn(1, {}, 3);緩存的垃圾回收
緩存數(shù)據(jù)時,要注意緩存失效但是引用的數(shù)據(jù)沒有釋放造成的內(nèi)存泄漏問題。
所以,對于引用類型數(shù)據(jù),可以使用WeakMap保存。
對于原始類型數(shù)據(jù),可以使用Map保存。
WeakMap與Map的區(qū)別在于 —— 在WeakMap中,key到他對應(yīng)的value是弱引用。這意味著當沒有其他數(shù)據(jù)引用這個key時,他可以被垃圾回收。而在Map中,key到value是強引用,即使沒有其他數(shù)據(jù)引用這個key,他也不會被垃圾回收。
實現(xiàn)原理
本文不會介紹具體的代碼實現(xiàn)(大段貼代碼讓人看起來頭疼)。
我會用示例圖講解實現(xiàn)原理。了解原理后,如果你對實現(xiàn)細節(jié)感興趣,可以參考:
對于如下代碼:
const cacheFn = cache(fn);
const obj = {};
cacheFn(1, obj, 3);cacheFn的每個傳參,對應(yīng)cache內(nèi)部的一個cacheNode節(jié)點:
// CacheNode構(gòu)造函數(shù)
function createCacheNode<T>(): CacheNode<T> {
return {
s: UNTERMINATED,
v: undefined,
o: null,
p: null
};
}字段的意義如下:
- s:
cacheNode的緩存狀態(tài),有 未中止/中止/發(fā)生錯誤 3種狀態(tài) - v:
cacheNode緩存的值 - o:緩存的引用類型值
- p:緩存的原始類型值
上述cacheFn執(zhí)行后會生成如下cacheNode鏈式結(jié)構(gòu):

讓我們看看這個鏈式結(jié)構(gòu)如何解決文章開篇提到的3個問題。
如何解決參數(shù)的順序?
可以看到,上圖中最后一個cacheNode節(jié)點的狀態(tài)(cacheNode.s)為中止。
如果后續(xù)執(zhí)行cacheFn傳入相同的參數(shù),則會復用緩存的cacheNode節(jié)點。
如果所有傳參都相同,那么會復用完整的cacheNode鏈,此時最后一個cacheNode節(jié)點為中止狀態(tài),則不需要重新執(zhí)行cacheFn方法計算返回值,而是直接返回緩存的值(cacheNode.v)。
如果后續(xù)執(zhí)行cacheFn,傳入新的參數(shù),則前后的cacheNode鏈不會一致。
比如:
// 第一次 cacheFn(1, obj, 3); // 第二次 cacheFn(1, 3, obj);
則第二次生成的cacheNode鏈中,第二個節(jié)點就與之前不同(之前obj,之后3),則后續(xù)cacheNode節(jié)點也不會相同。

通過這種鏈式結(jié)構(gòu),保證了只有當所有參數(shù)保持一致,才能返回緩存的值。否則將重新執(zhí)行函數(shù),并緩存新的返回值與cacheNode鏈。
如何處理引用類型值
可以從圖中發(fā)現(xiàn),對于引用類型參數(shù)(比如示例中的obj),對應(yīng)一個weakMap節(jié)點。
這不僅意味著當沒有其他數(shù)據(jù)引用他時,這個cacheNode節(jié)點能夠釋放內(nèi)存,同時也意味著這個cacheNode之后的cacheNode鏈會斷掉,他們占用的內(nèi)存也會釋放。
而原始類型值不存在這樣的問題,從圖中可以發(fā)現(xiàn),原始類型值對應(yīng)一個map節(jié)點。
總結(jié)
cache方法是React內(nèi)部實現(xiàn),未來會暴露給開發(fā)者使用的緩存方法,可以緩存任意函數(shù)。
當多次執(zhí)行并傳遞相同的參數(shù)給cache包裹的函數(shù)時,后續(xù)執(zhí)行會返回緩存的值。
這是為了應(yīng)對某些函數(shù)需要在React組件多次render間返回穩(wěn)定的值的場景。
比如:對于相同的傳參,請求數(shù)據(jù)的函數(shù)返回同一個promise。
cache的實現(xiàn)方式是 —— 基于傳參,構(gòu)造一條cacheNode鏈,傳參的穩(wěn)定對應(yīng)了鏈表的穩(wěn)定,并最終對應(yīng)了返回值的穩(wěn)定。
以上就是React內(nèi)部實現(xiàn)cache方法示例詳解的詳細內(nèi)容,更多關(guān)于React內(nèi)部實現(xiàn)cache方法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React中useTransition鉤子函數(shù)的使用詳解
React?18的推出標志著React并發(fā)特性的正式到來,其中useTransition鉤子函數(shù)是一個重要的新增功能,下面我們就來學習一下useTransition鉤子函數(shù)的具體使用吧2024-02-02
react如何同步獲取useState的最新狀態(tài)值
這篇文章主要介紹了react如何同步獲取useState的最新狀態(tài)值問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
React函數(shù)式組件Hook中的useEffect函數(shù)的詳細解析
useEffect是react v16.8新引入的特性。我們可以把useEffect hook看作是componentDidMount、componentDidUpdate、componentWillUnmounrt三個函數(shù)的組合2022-10-10
React Native實現(xiàn)進度條彈框的示例代碼
本篇文章主要介紹了React Native實現(xiàn)進度條彈框的示例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07
關(guān)于React中使用window.print()出現(xiàn)頁面無響應(yīng)問題解決記錄
這篇文章主要介紹了React中使用window.print()出現(xiàn)頁面無響應(yīng)問題解決記錄,首先問題原因可能是操作了document但是并未進行銷毀(可能是),具體問題解決思路參考下本文吧2021-11-11
React中映射一個嵌套數(shù)組實現(xiàn)demo
這篇文章主要為大家介紹了React中映射一個嵌套數(shù)組實現(xiàn)demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12

