React超詳細(xì)分析useState與useReducer源碼
熱身準(zhǔn)備
在正式講useState,我們先熱熱身,了解下必備知識。
為什么會(huì)有hooks
大家都知道hooks是在函數(shù)組件的產(chǎn)物。之前class組件為什么沒有出現(xiàn)hooks這種東西呢?
答案很簡單,不需要。
因?yàn)樵?code>class組件中,在運(yùn)行時(shí),只會(huì)生成一個(gè)實(shí)例,而在這個(gè)實(shí)例中會(huì)保存組件的state等信息。在后續(xù)的更新操作中,也只是調(diào)用其中的render方法,實(shí)例中的信息不會(huì)丟失。而在函數(shù)組件中,每次渲染,更新都會(huì)去執(zhí)行這個(gè)函數(shù)組件,所以在函數(shù)組件中是沒辦法保存state等信息的。為了保存state等信息,于是有了hooks,用來記錄函數(shù)組件的狀態(tài),執(zhí)行副作用。
hooks執(zhí)行時(shí)機(jī)
上面提到,在函數(shù)組件中,每次渲染,更新都會(huì)去執(zhí)行這個(gè)函數(shù)組件。所以我們在函數(shù)組件內(nèi)部聲明的hooks也會(huì)在每次執(zhí)行函數(shù)組件時(shí)執(zhí)行。
在這個(gè)時(shí)候,可能有的同學(xué)聽了我上面的說法(hooks用來記錄函數(shù)組件的狀態(tài),執(zhí)行副作用),又有疑惑了,既然每次函數(shù)組件執(zhí)行都會(huì)執(zhí)行hooks方法,那hooks是怎么記錄函數(shù)組件的狀態(tài)的呢?
答案是,記錄在函數(shù)組件對應(yīng)的fiber節(jié)點(diǎn)中。
兩套hooks
在我們剛開始學(xué)習(xí)使用hooks時(shí),可能會(huì)有疑惑, 為什么hooks要在函數(shù)組件的頂部聲明,而不能在條件語句或內(nèi)部函數(shù)中聲明?
答案是,React維護(hù)了兩套hooks,一套用來在項(xiàng)目初始化mount時(shí),初始化hooks。而在后續(xù)的更新操作中會(huì)基于初始化的hooks執(zhí)行更新操作。如果我們在條件語句或函數(shù)中聲明hooks,有可能在項(xiàng)目初始化時(shí)不會(huì)聲明,這樣就會(huì)導(dǎo)致在后面的更新操作中出問題。
hooks存儲(chǔ)
提前講一下hooks存儲(chǔ)方式,避免看暈了~~~
每個(gè)初始化的hook都會(huì)創(chuàng)建一個(gè)hook結(jié)構(gòu),多個(gè)hook是通過聲明順序用鏈表的結(jié)構(gòu)相關(guān)聯(lián),最終這個(gè)鏈表會(huì)存放在fiber.memoizedState中:
var hook = {
memoizedState: null, // 存儲(chǔ)hook操作,不要和fiber.memoizedState搞混了
baseState: null,
baseQueue: null,
queue: null, // 存儲(chǔ)該hook本次更新階段的所有更新操作
next: null // 鏈接下一個(gè)hook
};
而在每個(gè)hook.queue中存放的么個(gè)update也是一個(gè)鏈表結(jié)構(gòu)存儲(chǔ)的,千萬不要和hook的鏈表搞混了。
接下來,讓我們帶著下面幾個(gè)問題看文章:
- 為什么
setState后不能馬上拿到最新的state的值? - 多個(gè)
setState是如何合并的? setState到底是同步還是異步的?- 為什么
setState的值相同時(shí),函數(shù)組件不更新?
假如我們有下面這樣一段代碼:
function App(){
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count => count + 1)
}
return (
<div>
勇敢牛牛, <span>不怕困難</span>
<span onClick={handleClick}>{count}</span>
</div>
)
}初始化 mount
useState
我們先來看下useState()函數(shù):
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
上面的dispatcher就會(huì)涉及到開始提到的兩套hooks的變換使用,initialState是我們傳入useState的參數(shù),可以是基礎(chǔ)數(shù)據(jù)類型,也可以是函數(shù),我們主要看dispatcher.useState(initialState)方法,因?yàn)槲覀冞@里是初始化,它會(huì)調(diào)用mountState方法:相關(guān)參考視頻:傳送門
function mountState(initialState) {
var hook = mountWorkInProgressHook(); // workInProgressHook
if (typeof initialState === 'function') {
// 在這里,如果我們傳入的參數(shù)是函數(shù),會(huì)執(zhí)行拿到return作為initialState
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
var queue = hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState
};
var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);
return [hook.memoizedState, dispatch];
}上面的代碼還是比較簡單,主要就是根據(jù)useState()的入?yún)⑸梢粋€(gè)queue并保存在hook中,然后將入?yún)⒑徒壎藘蓚€(gè)參數(shù)的dispatchAction作為返回值暴露到函數(shù)組件中去使用。
這兩個(gè)返回值,第一個(gè)hook.memoizedState比較好理解,就是初始值,第二個(gè)dispatch,也就是dispatchAction.bind(null, currentlyRenderingFiber$1, queue)這是個(gè)什么東西呢?
我們知道使用useState()方法會(huì)返回兩個(gè)值state, setState,這個(gè)setState就對應(yīng)上面的dispatchAction,這個(gè)函數(shù)是怎么做到幫我們設(shè)置state的值的呢?
我們先保留這個(gè)疑問,往下看,在后面會(huì)慢慢揭曉答案。
接下來我們主要看看mountWorkInProgressHook都做了些什么。
mountWorkInProgressHook
function mountWorkInProgressHook() {
var hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
};
// 這里的if/else主要用來區(qū)分是否是第一個(gè)hook
if (workInProgressHook === null) {
currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
} else {
// 把hook加到hooks鏈表的最后一條, 并且指針指向這條hook
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}從上面的currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;這一行代碼,我們可以發(fā)現(xiàn),hook是存放在對應(yīng)fiber.memoizedState上的。
workInProgressHook = workInProgressHook.next = hook; ,從這一行代碼,我們能知道,如果是有多個(gè)hook,他們是以鏈表的形式進(jìn)行的存放。
不僅僅是useState()這個(gè)hook會(huì)在初始化時(shí)走mountWorkInProgressHook方法,其他的hook,例如:useEffect, useRef, useCallback等在初始化時(shí)都是調(diào)用的這個(gè)方法。
到這里我們能搞明白兩件事:
hooks的狀態(tài)數(shù)據(jù)是存放在對應(yīng)的函數(shù)組件的fiber.memoizedState;- 一個(gè)函數(shù)組件上如果有多個(gè)
hook,他們會(huì)通過聲明的順序以鏈表的結(jié)構(gòu)存儲(chǔ);
到這里,我們的useState()已經(jīng)完成了它初始化時(shí)的所有工作了,簡單概括下,useState()在初始化時(shí)會(huì)將我們傳入的初始值以hook的結(jié)構(gòu)存放到對應(yīng)的fiber.memoizedState,以數(shù)組形式返回[state, dispatchAction]。
更新update
當(dāng)我們以某種形式觸發(fā)setState()時(shí),React也會(huì)根據(jù)setState()的值來決定如何更新視圖。
在上面講到,useState在初始化時(shí)會(huì)返回[state, dispatchAction],那我們調(diào)用setState()方法,實(shí)際上就是調(diào)用dispatchAction,而且這個(gè)函數(shù)在初始化時(shí)還通過bind綁定了兩個(gè)參數(shù), 一個(gè)是useState初始化時(shí)函數(shù)組件對應(yīng)的fiber,另一個(gè)是hook結(jié)構(gòu)的queue。
來看下我精簡后的dispatchAction(去除了和setState無關(guān)的代碼)
function dispatchAction(fiber, queue, action) {
// 創(chuàng)建一個(gè)update,用于后續(xù)的更新,這里的action就是我們setState的入?yún)?
var update = {
lane: lane,
action: action,
eagerReducer: null,
eagerState: null,
next: null
};
// 這段閉環(huán)鏈表插入update的操作有沒有很熟悉?
var pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
var alternate = fiber.alternate;
// 判斷當(dāng)前是否是渲染階段
if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
var lastRenderedReducer = queue.lastRenderedReducer;
// 這個(gè)if語句里的一大段就是用來判斷我們這次更新是否和上次一樣,如果一樣就不會(huì)在進(jìn)行調(diào)度更新
if (lastRenderedReducer !== null) {
var prevDispatcher;
{
prevDispatcher = ReactCurrentDispatcher$1.current;
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
}
try {
var currentState = queue.lastRenderedState;
var eagerState = lastRenderedReducer(currentState, action);
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (objectIs(eagerState, currentState)) {
return;
}
} finally {
{
ReactCurrentDispatcher$1.current = prevDispatcher;
}
}
}
}
// 將攜帶有update的fiber進(jìn)行調(diào)度更新
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
}上面的代碼已經(jīng)是我盡力精簡的結(jié)果了。。。代碼上有注釋,各位看官湊合看下。
不愿細(xì)看的我來總結(jié)下dispatchAction做的事情:
- 創(chuàng)建一個(gè)
update并加入到fiber.hook.queue鏈表中,并且鏈表指針指向這個(gè)update; - 判斷當(dāng)前是否是渲染階段決定要不要馬上調(diào)度更新;
- 判斷這次的操作和上次的操作是否相同, 如果相同則不進(jìn)行調(diào)度更新;
- 滿足上述條件則將帶有
update的fiber進(jìn)行調(diào)度更新;
到這里我們又搞明白了一個(gè)問題:
為什么setState的值相同時(shí),函數(shù)組件不更新?
updateState
我們這里不詳細(xì)講解調(diào)度更新的過程, 后面文章安排, 這里我們只需要知道,在接下來更新過程中,會(huì)再次執(zhí)行我們的函數(shù)組件,這時(shí)又會(huì)調(diào)用useState方法了。前面講過,React維護(hù)了兩套hooks,一套用于初始化, 一套用于更新。 這個(gè)在調(diào)度更新時(shí)就已經(jīng)完成了切換。所以我們這次調(diào)用useState方法會(huì)和之前初始化有所不同。
這次我們進(jìn)入useState,會(huì)看到其實(shí)是調(diào)用的updateState方法
function updateState(initialState) {
return updateReducer(basicStateReducer);
}
看到這幾行代碼,看官們應(yīng)該就明白為什么網(wǎng)上有人說useState和useReducer相似。原來在useState的更新中調(diào)用的就是updateReducer啊。
updateReducer
本來很長,想讓各位看官忍一忍。于心不忍,忍痛減了很多
function updateReducer(reducer, initialArg, init) {
// 創(chuàng)建一個(gè)新的hook,帶有dispatchAction創(chuàng)建的update
var hook = updateWorkInProgressHook();
var queue = hook.queue;
queue.lastRenderedReducer = reducer;
var current = currentHook;
var baseQueue = current.baseQueue;
var pendingQueue = queue.pending;
current.baseQueue = baseQueue = pendingQueue;
if (baseQueue !== null) {
// 從這里能看到之前講的創(chuàng)建閉環(huán)鏈表插入update的好處了吧?直接next就能找到第一個(gè)update
var first = baseQueue.next;
var newState = current.baseState;
var update = first;
// 開始遍歷update鏈表執(zhí)行所有setState
do {
var updateLane = update.lane;
// 假如我們這個(gè)update上有多個(gè)setState,在循環(huán)過程中,最終都會(huì)做合并操作
var action = update.action;
// 這里的reducer會(huì)判斷action類型,下面講
newState = reducer(newState, action);
update = update.next;
} while (update !== null && update !== first);
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
var dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}上面的更新中,會(huì)循環(huán)遍歷update進(jìn)行一個(gè)合并操作,只取最后一個(gè)setState的值,這時(shí)候可能有人會(huì)問那直接取最后一個(gè)setState的值不是更方便嗎?
這樣做是不行的,因?yàn)?code>setState入?yún)⒖梢允腔A(chǔ)類型也可以是函數(shù), 如果傳入的是函數(shù),它會(huì)依賴上一個(gè)setState的值來完成更新操作,下面的代碼就是上面的循環(huán)中的reducer
function basicStateReducer(state, action) {
return typeof action === 'function' ? action(state) : action;
}
到這里我們搞明白了一個(gè)問題,多個(gè)setState是如何合并的?
updateWorkInProgressHook
下面是偽代碼,我把很多的邏輯判斷給刪除了,免了太長又讓各位看官難受,原來的代碼里會(huì)判斷當(dāng)前的hook是不是第一個(gè)調(diào)度更新的hook,我這里為了簡單就按第一個(gè)來解析
function updateWorkInProgressHook() {
var nextCurrentHook;
nextCurrentHook = current.memoizedState;
var newHook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null
}
currentlyRenderingFiber$1.memoizedState = workInProgressHook = newHook;
return workInProgressHook;
}從上面代碼能看出來,updateWorkInProgressHook拋去那些判斷, 其實(shí)做的事情也很簡單,就是基于fiber.memoizedState創(chuàng)建一個(gè)新的hook結(jié)構(gòu)覆蓋之前的hook。前面dispatchAction講到會(huì)把update加入到hook.queue中,在這里的newHook.queue上就有這個(gè)update。
總結(jié)
總結(jié)下useState初始化和setState更新:
useState會(huì)在第一次執(zhí)行函數(shù)組件時(shí)進(jìn)行初始化,返回[state, dispatchAction]。- 當(dāng)我們通過
setState也就是dispatchAction進(jìn)行調(diào)度更新時(shí),會(huì)創(chuàng)建一個(gè)update加入到hook.queue中。 - 當(dāng)更新過程中再次執(zhí)行函數(shù)組件,也會(huì)調(diào)用
useState方法,此時(shí)的useState內(nèi)部會(huì)使用更新時(shí)的hooks。 - 通過
updateWorkInProgressHook獲取到dispatchAction創(chuàng)建的update。 - 在
updateReducer通過遍歷update鏈表完成setState合并。 - 返回
update后的[newState, dispatchAction].
還有兩個(gè)問題
為什么setState后不能馬上拿到最新的state的值? React其實(shí)可以這么做,為什么沒有這么做,因?yàn)槊總€(gè)setState都會(huì)觸發(fā)更新,React出于性能考慮,會(huì)做一個(gè)合并操作。所以setState只是觸發(fā)了dispatchAction生成了一個(gè)update的動(dòng)作,新的state會(huì)存儲(chǔ)在update中,等到下一次render, 觸發(fā)這個(gè)useState所在的函數(shù)組件執(zhí)行,才會(huì)賦值新的state。
setState到底是同步還是異步的?
同步的,假如我們有這樣一段代碼:
const handleClick = () => {
setCount(2)
setCount(count => count + 1)
console.log('after setCount')
}
你會(huì)驚奇的發(fā)現(xiàn)頁面還沒有更新count,但是控制臺(tái)已經(jīng)打印了after setCount。
之所以表現(xiàn)上像是異步,是因?yàn)閮?nèi)部使用了try{...}finally{...}。當(dāng)調(diào)用setState觸發(fā)調(diào)度更新時(shí),更新操作會(huì)放在finally中,返回去繼續(xù)執(zhí)行handlelick的邏輯。于是會(huì)出現(xiàn)上面的情況。
看完這篇文章, 我們可以弄明白下面這幾個(gè)問題:
- 為什么
setState后不能馬上拿到最新的state的值? - 多個(gè)
setState是如何合并的? setState到底是同步還是異步的?- 為什么
setState的值相同時(shí),函數(shù)組件不更新? setState是怎么完成更新的?useState是什么時(shí)候初始化又是什么時(shí)候開始更新的?
到此這篇關(guān)于React超詳細(xì)分析useState與useReducer源碼的文章就介紹到這了,更多相關(guān)React useState與useReducer內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
40行代碼把Vue3的響應(yīng)式集成進(jìn)React做狀態(tài)管理
這篇文章主要介紹了40行代碼把Vue3的響應(yīng)式集成進(jìn)React做狀態(tài)管理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
React?中使用?RxJS?優(yōu)化數(shù)據(jù)流的處理方案
這篇文章主要為大家介紹了React?中使用?RxJS?優(yōu)化數(shù)據(jù)流的處理方案示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
React項(xiàng)目中decorators裝飾器報(bào)錯(cuò)問題解決方案
這篇文章主要介紹了React項(xiàng)目中decorators裝飾器報(bào)錯(cuò),本文給大家分享問題所在原因及解決方案,通過圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01

