您好,登錄后才能下訂單哦!
這篇文章主要介紹“useState執(zhí)行流程使怎樣的”,在日常操作中,相信很多人在useState執(zhí)行流程使怎樣的問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”useState執(zhí)行流程使怎樣的”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
作為 React 開發(fā)者,你能答上如下兩個問題么:
對于如下函數(shù)組件:
function App() { const [num, updateNum] = useState(0); window.updateNum = updateNum; return num; }
調(diào)用window.updateNum(1)
可以將視圖中的0
更新為1
么?
對于如下函數(shù)組件:
function App() { const [num, updateNum] = useState(0); function increment() { setTimeout(() => { updateNum(num + 1); }, 1000); } return <p onClick={increment}>{num}</p>; }
在1秒內(nèi)快速點擊p
5次,視圖上顯示為幾?
1. 可以 2. 顯示為1
其實,這兩個問題本質(zhì)上是在問:
useState
如何保存狀態(tài)?
useState
如何更新狀態(tài)?
本文會結(jié)合源碼,講透如上兩個問題。
這些,就是你需要了解的關于useState
的一切。
FunctionComponent
的render
本身只是函數(shù)調(diào)用。
那么在render
內(nèi)部調(diào)用的hook
是如何獲取到對應數(shù)據(jù)呢?
比如:
useState
獲取state
useRef
獲取ref
useMemo
獲取緩存的數(shù)據(jù)
答案是:
每個組件有個對應的fiber節(jié)點
(可以理解為虛擬DOM
),用于保存組件相關信息。
每次FunctionComponent
render
時,全局變量currentlyRenderingFiber
都會被賦值為該FunctionComponent
對應的fiber節(jié)點
。
所以,hook
內(nèi)部其實是從currentlyRenderingFiber
中獲取狀態(tài)信息的。
我們知道,一個FunctionComponent
中可能存在多個hook
,比如:
function App() { // hookA const [a, updateA] = useState(0); // hookB const [b, updateB] = useState(0); // hookC const ref = useRef(0); return <p></p>; }
那么多個hook
如何獲取自己的數(shù)據(jù)呢?
答案是:
currentlyRenderingFiber.memoizedState
中保存一條hook
對應數(shù)據(jù)的單向鏈表。
對于如上例子,可以理解為:
const hookA = { // hook保存的數(shù)據(jù) memoizedState: null, // 指向下一個hook next: hookB // ...省略其他字段 }; hookB.next = hookC; currentlyRenderingFiber.memoizedState = hookA;
當FunctionComponent
render
時,每執(zhí)行到一個hook
,都會將指向currentlyRenderingFiber.memoizedState
鏈表的指針向后移動一次,指向當前hook
對應數(shù)據(jù)。
這也是為什么React
要求hook
的調(diào)用順序不能改變(不能在條件語句中使用hook
) —— 每次render
時都是從一條固定順序的鏈表中獲取hook
對應數(shù)據(jù)的。
我們知道,useState
返回值數(shù)組第二個參數(shù)為改變state的方法。
在源碼中,他被稱為dispatchAction
。
每當調(diào)用dispatchAction
,都會創(chuàng)建一個代表一次更新的對象update
:
const update = { // 更新的數(shù)據(jù) action: action, // 指向下一個更新 next: null };
對于如下例子
function App() { const [num, updateNum] = useState(0); function increment() { updateNum(num + 1); } return <p onClick={increment}>{num}</p>; }
調(diào)用updateNum(num + 1)
,會創(chuàng)建:
const update = { // 更新的數(shù)據(jù) action: 1, // 指向下一個更新 next: null // ...省略其他字段 };
如果是多次調(diào)用dispatchAction
,例如:
function increment() { // 產(chǎn)生update1 updateNum(num + 1); // 產(chǎn)生update2 updateNum(num + 2); // 產(chǎn)生update3 updateNum(num + 3); }
那么,update
會形成一條環(huán)狀鏈表。
update3 --next--> update1 ^ | | update2 |______next_______|
這條鏈表保存在哪里呢?
既然這條update
鏈表是由某個useState
的dispatchAction
產(chǎn)生,那么這條鏈表顯然屬于該useState hook
。
我們繼續(xù)補充hook
的數(shù)據(jù)結(jié)構(gòu)。
const hook = { // hook保存的數(shù)據(jù) memoizedState: null, // 指向下一個hook next: hookForB // 本次更新以baseState為基礎計算新的state baseState: null, // 本次更新開始時已有的update隊列 baseQueue: null, // 本次更新需要增加的update隊列 queue: null, };
其中,queue
中保存了本次更新update
的鏈表。
在計算state
時,會將queue
的環(huán)狀鏈表剪開掛載在baseQueue
最后面,baseQueue
基于baseState
計算新的state
。
在計算state
完成后,新的state
會成為memoizedState
。
為什么更新不基于
memoizedState
而是baseState
,是因為state
的計算過程需要考慮優(yōu)先級,可能有些update
優(yōu)先級不夠被跳過。所以memoizedState
并不一定和baseState
相同。
回到我們開篇第一個問題:
function App() { const [num, updateNum] = useState(0); window.updateNum = updateNum; return num; }
調(diào)用window.updateNum(1)
可以將視圖中的0
更新為1
么?
我們需要看看這里的updateNum
方法的具體實現(xiàn):
updateNum === dispatchAction.bind(null, currentlyRenderingFiber, queue);
可見,updateNum
方法即綁定了currentlyRenderingFiber
與queue
(即hook.queue
)的dispatchAction
。
上文已經(jīng)介紹,調(diào)用dispatchAction
的目的是生成update
,并插入到hook.queue
鏈表中。
既然queue
作為預置參數(shù)已經(jīng)綁定給dispatchAction
,那么調(diào)用dispatchAction
就步僅局限在FunctionComponent
內(nèi)部了。
第二個問題
function App() { const [num, updateNum] = useState(0); function increment() { setTimeout(() => { updateNum(num + 1); }, 1000); } return <p onClick={increment}>{num}</p>; }
在1秒內(nèi)快速點擊p
5次,視圖上顯示為幾?
我們知道,調(diào)用updateNum
會產(chǎn)生update
,其中傳參會成為update.action
。
在1秒內(nèi)點擊5次。在點擊第五次時,第一次點擊創(chuàng)建的update
還沒進入更新流程,所以hook.baseState
還未改變。
那么這5次點擊產(chǎn)生的update
都是基于同一個baseState
計算新的state
,并且num
變量也還未變化(即5次update.action
(即num + 1
)為同一個值)。
所以,最終渲染的結(jié)果為1。
那么,如何5次點擊讓視圖從1逐步變?yōu)?呢?
由以上知識我們知道,需要改變baseState
或者action
。
其中baseState
由 React 的更新流程決定,我們無法控制。
但是我們可以控制action
。
action
不僅可以傳值
,也可以傳函數(shù)
。
// action為值 updateNum(num + 1); // action為函數(shù) updateNum(num => num + 1);
在基于baseState
與update
鏈表生成新state
的過程中:
let newState = baseState; let firstUpdate = hook.baseQueue.next; let update = firstUpdate; // 遍歷baseQueue中的每一個update do { if (typeof update.action === 'function') { newState = update.action(newState); } else { newState = action; } } while (update !== firstUpdate)
可見,當傳值
時,由于我們5次action
為同一個值,所以最終計算的newState
也為同一個值。
而傳函數(shù)
時,newState
基于action
函數(shù)計算5次,則最終得到累加的結(jié)果。
如果這個例子中,我們使用useReducer
而不是useState
,由于useReducer
的action
始終為函數(shù)
,所以不會遇到我們例子中的問題。
事實上,useState
本身就是預置了如下reducer
的useReducer
。
function basicStateReducer(state, action) { return typeof action === 'function' ? action(state) : action; }
到此,關于“useState執(zhí)行流程使怎樣的”的學習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。