溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

關(guān)于useState的知識(shí)點(diǎn)有哪些

發(fā)布時(shí)間:2021-10-28 15:18:33 來(lái)源:億速云 閱讀:208 作者:iii 欄目:web開(kāi)發(fā)

本篇內(nèi)容介紹了“關(guān)于useState的知識(shí)點(diǎn)有哪些”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

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)用順序不能改變(不能在條件語(yǔ)句中使用hook) &mdash;&mdash; 每次render時(shí)都是從一條固定順序的鏈表中獲取hook對(duì)應(yīng)數(shù)據(jù)的。

關(guān)于useState的知識(shí)點(diǎn)有哪些

useState執(zhí)行流程

我們知道,useState返回值數(shù)組第二個(gè)參數(shù)為改變state的方法。

在源碼中,他被稱(chēng)為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    // ...省略其他字段  };

如果是多次調(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ì)形成一條環(huán)狀鏈表。

update3 --next--> update1    ^                 |    |               update2    |______next_______|

這條鏈表保存在哪里呢?

既然這條update鏈表是由某個(gè)useState的dispatchAction產(chǎn)生,那么這條鏈表顯然屬于該useState hook。

我們繼續(xù)補(bǔ)充hook的數(shù)據(jù)結(jié)構(gòu)。

const hook = {    // hook保存的數(shù)據(jù)    memoizedState: null,    // 指向下一個(gè)hook    next: hookForB    // 本次更新以baseState為基礎(chǔ)計(jì)算新的state    baseState: null,    // 本次更新開(kāi)始時(shí)已有的update隊(duì)列    baseQueue: null,    // 本次更新需要增加的update隊(duì)列    queue: null,  };

其中,queue中保存了本次更新update的鏈表。

在計(jì)算state時(shí),會(huì)將queue的環(huán)狀鏈表剪開(kāi)掛載在baseQueue最后面,baseQueue基于baseState計(jì)算新的state。

關(guān)于useState的知識(shí)點(diǎn)有哪些

在計(jì)算state完成后,新的state會(huì)成為memoizedState。

為什么更新不基于memoizedState而是baseState,是因?yàn)閟tate的計(jì)算過(guò)程需要考慮優(yōu)先級(jí),可能有些update優(yōu)先級(jí)不夠被跳過(guò)。所以memoizedState并不一定和baseState相同。更詳細(xì)的解釋見(jiàn)React技術(shù)揭秘[1]

回到我們開(kāi)篇第一個(gè)問(wèn)題:

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);

可見(jiàn),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

第二個(gè)問(wèn)題

function App() {    const [num, updateNum] = useState(0);    function increment() {      setTimeout(() => {        updateNum(num + 1);      }, 1000);    }    return <p onClick={increment}>{num}</p>;  }

在1秒內(nèi)快速點(diǎn)擊p5次,視圖上顯示為幾?

我們知道,調(diào)用updateNum會(huì)產(chǎn)生update,其中傳參會(huì)成為update.action。

在1秒內(nèi)點(diǎn)擊5次。在點(diǎn)擊第五次時(shí),第一次點(diǎn)擊創(chuàng)建的update還沒(méi)進(jìn)入更新流程,所以hook.baseState還未改變。

那么這5次點(diǎn)擊產(chǎn)生的update都是基于同一個(gè)baseState計(jì)算新的state,并且num變量也還未變化(即5次update.action(即num + 1)為同一個(gè)值)。

所以,最終渲染的結(jié)果為1。

useState與useReducer

那么,如何5次點(diǎn)擊讓視圖從1逐步變?yōu)?呢?

由以上知識(shí)我們知道,需要改變baseState或者action。

其中baseState由React的更新流程決定,我們無(wú)法控制。

但是我們可以控制action。

action不僅可以傳值,也可以傳函數(shù)。

// action為值  updateNum(num + 1);  // action為函數(shù)  updateNum(num => num + 1);

在基于baseState與update鏈表生成新state的過(guò)程中:

let newState = baseState;  let firstUpdate = hook.baseQueue.next;  let update = firstUpdate; // 遍歷baseQueue中的每一個(gè)update  do {    if (typeof update.action === 'function') {      newState = update.action(newState);    } else {      newState = action;    }  } while (update !== firstUpdate)

可見(jiàn),當(dāng)傳值時(shí),由于我們5次action為同一個(gè)值,所以最終計(jì)算的newState也為同一個(gè)值。

而傳函數(shù)時(shí),newState基于action函數(shù)計(jì)算5次,則最終得到累加的結(jié)果。

如果這個(gè)例子中,我們使用useReducer而不是useState,由于useReducer的action始終為函數(shù),所以不會(huì)遇到我們例子中的問(wèn)題。

事實(shí)上,useState本身就是預(yù)置了如下reducer的useReducer。

function basicStateReducer(state, action) {    return typeof action === 'function' ? action(state) : action;  }

“關(guān)于useState的知識(shí)點(diǎn)有哪些”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI