React源碼分析之useCallback與useMemo及useContext詳解
熱身準(zhǔn)備
useCallback和useMemo是一樣的東西,只是入?yún)⒂兴煌?/p>
useCallback緩存的是回調(diào)函數(shù),如果依賴項(xiàng)沒有更新,就會(huì)使用緩存的回調(diào)函數(shù);
useMemo緩存的是回調(diào)函數(shù)的return,如果依賴項(xiàng)沒有更新,就會(huì)使用緩存的return;
官網(wǎng)有這樣一段描述useCallback(fn, deps)相當(dāng)于useMemo(() => fn, deps)。
所以這里,只以useCallback為例進(jìn)行分析。
初始化mount
mountCallback
如果各位看官是系列文章第一篇開始看的,看到這里估計(jì)就無壓力,mountCallback就這幾行代碼,筆者沒有做精簡(jiǎn)。
function mountCallback(callback, deps) {
// 初始化hook結(jié)構(gòu)
var hook = mountWorkInProgressHook();
// 使用者傳進(jìn)來的依賴數(shù)組
var nextDeps = deps === undefined ? null : deps;
// 以數(shù)組的形式將回調(diào)和依賴數(shù)組存儲(chǔ)到對(duì)應(yīng)fiber.memoizedState.hook.moeoizedState
hook.memoizedState = [callback, nextDeps];
return callback;
}
更新 update
function updateCallback(callback, deps) {
var hook = updateWorkInProgressHook();
var nextDeps = deps === undefined ? null : deps;
var prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
var prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}updateCallback就這幾行代碼,沒有刪減,代碼意圖也很簡(jiǎn)單,如果依賴數(shù)組deps沒有變化,或者deps=[]的情況下,會(huì)返回之前緩存的回調(diào)函數(shù),否則就更新對(duì)應(yīng)fiber.memoizedState.hook.memoizedState并返回新的回調(diào)函數(shù)。
使用場(chǎng)景
就筆者的所見所聞,存在兩種極端情況,一種開發(fā)者在開發(fā)時(shí),不管什么函數(shù),什么數(shù)據(jù)都喜歡使用useCallback,useMemo進(jìn)行一層包裹。還有一種開發(fā)者不管什么情況都不會(huì)考慮使用useCallback,useMemo。
不用說,這兩種做法都是有問題的。第一種做法,還不知道是之所以會(huì)出現(xiàn)這樣的問題,根本原因還是很多開發(fā)者并不明白這兩個(gè)hook的原理和使用場(chǎng)景。
首先,我們要明確函數(shù)組件在每一次更新時(shí),都會(huì)執(zhí)行函數(shù)組件,函數(shù)組件內(nèi)部的所有方法,所有值都會(huì)重新聲明,重新計(jì)算。這兩個(gè)hook的出現(xiàn)就是為了優(yōu)化這種情況,避免不必要的浪費(fèi)。而這兩個(gè)hook的做法就是通過將函數(shù)或者值存儲(chǔ)在對(duì)應(yīng)的fiber.memoizedState.hook.memoizedState上,在下次更新時(shí),根據(jù)依賴項(xiàng)是否變化來決定是否要用緩存值,還是新的傳進(jìn)來的值。
這時(shí)候可能有人疑惑既然都會(huì)更新,那我全部包裹起來有什么不好?筆者認(rèn)為都進(jìn)行包裹主要的問題是,如果一個(gè)函數(shù)足夠簡(jiǎn)單,從新聲明可能性能消耗會(huì)比包裹后存儲(chǔ)在hook.memoizedState的消耗更小。
這里,筆者根據(jù)自己看源碼的心得,列舉下這兩個(gè)hook的使用場(chǎng)景:
- 如果子組件比較復(fù)雜,可以考慮使用
useCallback進(jìn)行包裹; - 如果函數(shù)組件中某個(gè)值需要大量的計(jì)算才能得出,可以考慮使用
useMemo進(jìn)行包裹; - 如果某個(gè)函數(shù)是子組件的props,可以考慮使用
useCallback進(jìn)行包裹(配合React.memo使用); - 自定義
hooks中復(fù)雜邏輯可以考慮使用useCallback和useMemo進(jìn)行包裹;
相關(guān)參考視頻講解:傳送門
總結(jié)
這兩個(gè)hook原理還是很簡(jiǎn)單的,因?yàn)槭窍盗形恼?,很多?nèi)容和前面文章都重復(fù)了,所以導(dǎo)致這篇都沒啥能寫的了??偨Y(jié)下原理:
這兩個(gè)hook的做法就是通過將函數(shù)或者值存儲(chǔ)在對(duì)應(yīng)的fiber.memoizedState.hook.memoizedState上,在下次更新時(shí),根據(jù)依賴項(xiàng)是否變化來決定是要用緩存值,還是新的傳進(jìn)來的值。
雖然useCallback和useMemo是為了優(yōu)化性能出現(xiàn)的,但是各位看官也不要盲目使用,畢竟這兩個(gè)hook本身也會(huì)帶來開銷。
看完這篇文章, 我們可以弄明白下面這幾個(gè)問題:
useCallback和useMemo的區(qū)別?useCallback和useMemo的使用場(chǎng)景有哪些?useCallback和useMemo是做什么的?useCallback和useMemo是怎么實(shí)現(xiàn)優(yōu)化性能的?
熱身準(zhǔn)備
useContext可以幫助我們跨越組件層級(jí)直接傳遞變量,避免了在每一個(gè)層級(jí)手動(dòng)的傳遞 props 屬性,實(shí)現(xiàn)共享,要配合createContext使用。
createContext
createContext主要功能是創(chuàng)建一個(gè)context,提供Provider和Consumer。Provider主要將context內(nèi)容暴露出來,Consumer可以拿到對(duì)應(yīng)context的Provider暴露的內(nèi)容使用。
示例代碼:
export const Context = createContext(null)
<Context.Provider value='initialValue'>
<Context.Consumer>
{(v) => { return <h2>{v}</h2>
}} </Context.Consumer>
</Context.Provider>Provider
<Context.Provider>在渲染時(shí),beginWork階段,會(huì)執(zhí)行
pushProvider(workInProgress, newValue);
它會(huì)將Provider的prop上的value字段存到context._currentValue中。
Consumer
<Context.Consumer>在渲染時(shí),beginWork階段,會(huì)執(zhí)行
prepareToReadContext(workInProgress, renderLanes); var newValue = readContext(context, newProps.unstable_observedBits);
通過上面代碼可以拿到Provider的prop上的value。
值得注意的是, Consumer標(biāo)簽下包裹的必須是一個(gè)函數(shù),如果不是函數(shù)會(huì)報(bào)錯(cuò)。 Consumer會(huì)將拿到的value作為函數(shù)的參數(shù)傳入函數(shù)中去使用。如同上面示例代碼中獲取到的v。
useContext
useContext需要將createContext創(chuàng)建的Context作為參數(shù)進(jìn)行調(diào)用。
值得一提的是,前面講的hook在初始化和更新時(shí)會(huì)有兩套不同函數(shù)執(zhí)行。但是在useContext只有一個(gè),也就是useContext在初始化和更新時(shí)執(zhí)行的是一套代碼。
初始化mount&更新update
useContext在mount時(shí)主要會(huì)調(diào)用readContext函數(shù):
function readContext(context, observedBits) {
var contextItem = {
context: context, // 傳入的context
observedBits: resolvedObservedBits, // 觀察范圍(默認(rèn)全部update)
next: null
};
lastContextDependency = contextItem;
currentlyRenderingFiber.dependencies = {
lanes: NoLanes,
firstContext: contextItem,
responders: null
};
} else {
// Append a new context item. lastContextDependency = lastContextDependency.next = contextItem;
}
return context._currentValue ;
}
精簡(jiǎn)了下代碼,可以看到,readContext會(huì)創(chuàng)建一個(gè)contextItem并以鏈表的結(jié)構(gòu)記錄在對(duì)應(yīng)fiber.dependencies上,最后將Provider的prop上的value返回。
總結(jié)
useContext的原理類似于觀察者模式。Provider是被觀察者, Consumer和useContext是觀察者。當(dāng)Provider上的值發(fā)生變化, 觀察者是可以觀察到的,從而同步信息給到組件。
主要使用場(chǎng)景就是多層級(jí)組件值的傳遞,如果值較多可以考慮配合useReducer使用。
看完這篇文章, 我們可以弄明白下面這個(gè)問題:
useContext的原理是什么?
到此這篇關(guān)于React源碼分析之useCallback與useMemo及useContext詳解的文章就介紹到這了,更多相關(guān)React useCallback useMemo useContext內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React項(xiàng)目如何使用Element的方法步驟
本文主要介紹了React項(xiàng)目如何使用Element的方法步驟,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
React Native之ListView實(shí)現(xiàn)九宮格效果的示例
本篇文章主要介紹了React Native之ListView實(shí)現(xiàn)九宮格效果的示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
React Native 搭建開發(fā)環(huán)境的方法步驟
本篇文章主要介紹了React Native 搭建開發(fā)環(huán)境的方法步驟,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10
解析TypeError:import_react_native.AppState.removeEventListener
這篇文章主要為大家介紹了TypeError:import_react_native.AppState.removeEventListener?is?not?a?function問題解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
react-native 實(shí)現(xiàn)漸變色背景過程
這篇文章主要介紹了react-native 實(shí)現(xiàn)漸變色背景過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09

