函數(shù)組件
function App() {
? const [num, updateNum] = useState(0);
? window.updateNum = updateNum;
? return num;
}
調(diào)用window.updateNum(1)可以將視圖中的0更新為1
對(duì)于如下函數(shù)組件
function App() {
? const [num, updateNum] = useState(0);
?
? function increment() {
? ? setTimeout(() => {
? ? ? updateNum(num + 1);
? ? }, 1000);
? }
?
? return <p onClick={increment}>{num}</p>;
}
點(diǎn)擊5次 結(jié)果仍為1
hook如何保存數(shù)據(jù)
FunctionComponent的render本身只是函數(shù)調(diào)用。
那么在render內(nèi)部調(diào)用的hook是如何獲取到對(duì)應(yīng)數(shù)據(jù)呢徙瓶?
比如:
useState獲取state
useRef獲取ref
useMemo獲取緩存的數(shù)據(jù)
答案是:
每個(gè)組件有個(gè)對(duì)應(yīng)的fiber節(jié)點(diǎn)(可以理解為虛擬DOM)掰读,用于保存組件相關(guān)信息萌抵。
每次FunctionComponent render時(shí),全局變量currentlyRenderingFiber都會(huì)被賦值為該FunctionComponent對(duì)應(yīng)的fiber節(jié)點(diǎn)艾蓝。
所以掘譬,hook內(nèi)部其實(shí)是從currentlyRenderingFiber中獲取狀態(tài)信息的。
多個(gè)hook如何獲取數(shù)據(jù)
我們知道猎拨,一個(gè)FunctionComponent中可能存在多個(gè)hook,比如:
function App() {
? // hookA
? const [a, updateA] = useState(0);
? // hookB
? const [b, updateB] = useState(0);
? // hookC
? const ref = useRef(0);
?
? return <p></p>;
}
那么多個(gè)hook如何獲取自己的數(shù)據(jù)呢屠阻?
答案是:
currentlyRenderingFiber.memoizedState中保存一條hook對(duì)應(yīng)數(shù)據(jù)的單向鏈表红省。
對(duì)于如上例子,可以理解為:
const hookA = {
? // hook保存的數(shù)據(jù)
? memoizedState: null,
? // 指向下一個(gè)hook
? next: hookB
? // ...省略其他字段
};
hookB.next = hookC;
currentlyRenderingFiber.memoizedState = hookA;
當(dāng)FunctionComponent render時(shí)国觉,每執(zhí)行到一個(gè)hook吧恃,都會(huì)將指向currentlyRenderingFiber.memoizedState鏈表的指針向后移動(dòng)一次,指向當(dāng)前hook對(duì)應(yīng)數(shù)據(jù)蛉加。
這也是為什么React要求hook的調(diào)用順序不能改變(不能在條件語句中使用hook) —— 每次render時(shí)都是從一條固定順序的鏈表中獲取hook對(duì)應(yīng)數(shù)據(jù)的蚜枢。
useState執(zhí)行流程
我們知道,useState返回值數(shù)組第二個(gè)參數(shù)為改變state的方法针饥。
在源碼中,他被稱為dispatchAction需频。
每當(dāng)調(diào)用dispatchAction丁眼,都會(huì)創(chuàng)建一個(gè)代表一次更新的對(duì)象update:
const update = {
? // 更新的數(shù)據(jù)
? action: action,
? // 指向下一個(gè)更新
? next: null
};
對(duì)于如下例子
function App() {
? const [num, updateNum] = useState(0);
?
? function increment() {
? ? updateNum(num + 1);
? }
?
? return <p onClick={increment}>{num}</p>;
}
調(diào)用updateNum(num + 1),會(huì)創(chuàng)建:
const update = {
? // 更新的數(shù)據(jù)
? action: 1,
? // 指向下一個(gè)更新
? next: null
? // ...省略其他字段
};