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