本篇內(nèi)容介紹了“關(guān)于useState的知識點(diǎn)有哪些”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
網(wǎng)站的建設(shè)創(chuàng)新互聯(lián)專注網(wǎng)站定制,經(jīng)驗(yàn)豐富,不做模板,主營網(wǎng)站定制開發(fā).小程序定制開發(fā),H5頁面制作!給你煥然一新的設(shè)計體驗(yàn)!已為混凝土攪拌站等企業(yè)提供專業(yè)服務(wù)。
hook如何保存數(shù)據(jù)
FunctionComponent的render本身只是函數(shù)調(diào)用。
那么在render內(nèi)部調(diào)用的hook是如何獲取到對應(yīng)數(shù)據(jù)呢?
比如:
useState獲取state
useRef獲取ref
useMemo獲取緩存的數(shù)據(jù)
答案是:
每個組件有個對應(yīng)的fiber節(jié)點(diǎn)(可以理解為虛擬DOM),用于保存組件相關(guān)信息。
每次FunctionComponent render時,全局變量currentlyRenderingFiber都會被賦值為該FunctionComponent對應(yīng)的fiber節(jié)點(diǎn)。
所以,hook內(nèi)部其實(shí)是從currentlyRenderingFiber中獲取狀態(tài)信息的。
多個hook如何獲取數(shù)據(jù)
我們知道,一個FunctionComponent中可能存在多個hook,比如:
function App() { // hookA const [a, updateA] = useState(0); // hookB const [b, updateB] = useState(0); // hookC const ref = useRef(0); return ; }
那么多個hook如何獲取自己的數(shù)據(jù)呢?
答案是:
currentlyRenderingFiber.memoizedState中保存一條hook對應(yīng)數(shù)據(jù)的單向鏈表。
對于如上例子,可以理解為:
const hookA = { // hook保存的數(shù)據(jù) memoizedState: null, // 指向下一個hook next: hookB // ...省略其他字段 }; hookB.next = hookC; currentlyRenderingFiber.memoizedState = hookA;
當(dāng)FunctionComponent render時,每執(zhí)行到一個hook,都會將指向currentlyRenderingFiber.memoizedState鏈表的指針向后移動一次,指向當(dāng)前hook對應(yīng)數(shù)據(jù)。
這也是為什么React要求hook的調(diào)用順序不能改變(不能在條件語句中使用hook) —— 每次render時都是從一條固定順序的鏈表中獲取hook對應(yīng)數(shù)據(jù)的。
useState執(zhí)行流程
我們知道,useState返回值數(shù)組第二個參數(shù)為改變state的方法。
在源碼中,他被稱為dispatchAction。
每當(dāng)調(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{num}
; }
調(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ù)補(bǔ)充hook的數(shù)據(jù)結(jié)構(gòu)。
const hook = { // hook保存的數(shù)據(jù) memoizedState: null, // 指向下一個hook next: hookForB // 本次更新以baseState為基礎(chǔ)計算新的state baseState: null, // 本次更新開始時已有的update隊(duì)列 baseQueue: null, // 本次更新需要增加的update隊(duì)列 queue: null, };
其中,queue中保存了本次更新update的鏈表。
在計算state時,會將queue的環(huán)狀鏈表剪開掛載在baseQueue最后面,baseQueue基于baseState計算新的state。
在計算state完成后,新的state會成為memoizedState。
為什么更新不基于memoizedState而是baseState,是因?yàn)閟tate的計算過程需要考慮優(yōu)先級,可能有些update優(yōu)先級不夠被跳過。所以memoizedState并不一定和baseState相同。更詳細(xì)的解釋見React技術(shù)揭秘[1]
回到我們開篇第一個問題:
function App() { const [num, updateNum] = useState(0); window.updateNum = updateNum; return num; }
調(diào)用window.updateNum(1)可以將視圖中的0更新為1么?
我們需要看看這里的updateNum方法的具體實(shí)現(xiàn):
updateNum === dispatchAction.bind(null, currentlyRenderingFiber, queue);
可見,updateNum方法即綁定了currentlyRenderingFiber與queue(即hook.queue)的dispatchAction。
上文已經(jīng)介紹,調(diào)用dispatchAction的目的是生成update,并插入到hook.queue鏈表中。
既然queue作為預(yù)置參數(shù)已經(jīng)綁定給dispatchAction,那么調(diào)用dispatchAction就步僅局限在FunctionComponent內(nèi)部了。
update的action
第二個問題
function App() { const [num, updateNum] = useState(0); function increment() { setTimeout(() => { updateNum(num + 1); }, 1000); } return{num}
; }
在1秒內(nèi)快速點(diǎn)擊p5次,視圖上顯示為幾?
我們知道,調(diào)用updateNum會產(chǎn)生update,其中傳參會成為update.action。
在1秒內(nèi)點(diǎn)擊5次。在點(diǎn)擊第五次時,第一次點(diǎn)擊創(chuàng)建的update還沒進(jìn)入更新流程,所以hook.baseState還未改變。
那么這5次點(diǎn)擊產(chǎn)生的update都是基于同一個baseState計算新的state,并且num變量也還未變化(即5次update.action(即num + 1)為同一個值)。
所以,最終渲染的結(jié)果為1。
useState與useReducer
那么,如何5次點(diǎn)擊讓視圖從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)
可見,當(dāng)傳值時,由于我們5次action為同一個值,所以最終計算的newState也為同一個值。
而傳函數(shù)時,newState基于action函數(shù)計算5次,則最終得到累加的結(jié)果。
如果這個例子中,我們使用useReducer而不是useState,由于useReducer的action始終為函數(shù),所以不會遇到我們例子中的問題。
事實(shí)上,useState本身就是預(yù)置了如下reducer的useReducer。
function basicStateReducer(state, action) { return typeof action === 'function' ? action(state) : action; }
“關(guān)于useState的知識點(diǎn)有哪些”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!