日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
關(guān)于useState的一切

作為React開發(fā)者,你能答上如下兩個問題么:

創(chuàng)新互聯(lián)是一家朝氣蓬勃的網(wǎng)站建設(shè)公司。公司專注于為企業(yè)提供信息化建設(shè)解決方案。從事網(wǎng)站開發(fā),網(wǎng)站制作,網(wǎng)站設(shè)計(jì),網(wǎng)站模板,微信公眾號開發(fā),軟件開發(fā),小程序設(shè)計(jì),十余年建站對濕噴機(jī)等多個行業(yè),擁有豐富的營銷推廣經(jīng)驗(yàn)。

    1.  對于如下函數(shù)組件:

 
 
 
 
  1. function App() {  
  2.   const [num, updateNum] = useState(0);  
  3.   window.updateNum = updateNum; 
  4.   return num;  

調(diào)用window.updateNum(1)可以將視圖中的0更新為1么?

    2.  對于如下函數(shù)組件:

 
 
 
 
  1. function App() {  
  2.   const [num, updateNum] = useState(0);  
  3.   function increment() {  
  4.     setTimeout(() => {  
  5.       updateNum(num + 1);  
  6.     }, 1000);  
  7.   }  
  8.   return {num}

    ;  

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

 
 
 
 
  1. 向右滑動展示答案                                             1. 可以  
  2.                                                             2. 顯示為1 

其實(shí),這兩個問題本質(zhì)上是在問:

  •  useState如何保存狀態(tài)?
  •  useState如何更新狀態(tài)?

本文會結(jié)合源碼,講透如上兩個問題。

這些,就是你需要了解的關(guān)于useState的一切。

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,比如:

 
 
 
 
  1. function App() {  
  2.   // hookA  
  3.   const [a, updateA] = useState(0);  
  4.   // hookB 
  5.   const [b, updateB] = useState(0);  
  6.   // hookC  
  7.   const ref = useRef(0); 
  8.    return 

    ;  

那么多個hook如何獲取自己的數(shù)據(jù)呢?

答案是:

currentlyRenderingFiber.memoizedState中保存一條hook對應(yīng)數(shù)據(jù)的單向鏈表。

對于如上例子,可以理解為:

 
 
 
 
  1. const hookA = {  
  2.   // hook保存的數(shù)據(jù)  
  3.   memoizedState: null,  
  4.   // 指向下一個hook  
  5.   next: hookB  
  6.   // ...省略其他字段  
  7. }; 
  8. hookB.next = hookC;  
  9. 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:

 
 
 
 
  1. const update = {  
  2.   // 更新的數(shù)據(jù)  
  3.   action: action,  
  4.   // 指向下一個更新  
  5.   next: null  
  6. }; 

對于如下例子

 
 
 
 
  1. function App() {  
  2.   const [num, updateNum] = useState(0);  
  3.   function increment() {  
  4.     updateNum(num + 1);  
  5.   }  
  6.   return {num}

    ;  

調(diào)用updateNum(num + 1),會創(chuàng)建:

 
 
 
 
  1. const update = {  
  2.   // 更新的數(shù)據(jù)  
  3.   action: 1,  
  4.   // 指向下一個更新  
  5.   next: null  
  6.   // ...省略其他字段  
  7. }; 

如果是多次調(diào)用dispatchAction,例如:

 
 
 
 
  1. function increment() {  
  2.   // 產(chǎn)生update1  
  3.   updateNum(num + 1);  
  4.   // 產(chǎn)生update2  
  5.   updateNum(num + 2);  
  6.   // 產(chǎn)生update3  
  7.   updateNum(num + 3);  }

那么,update會形成一條環(huán)狀鏈表。

 
 
 
 
  1. update3 --next--> update1  
  2.   ^                 |  
  3.   |               update2  
  4.   |______next_______|                       

 這條鏈表保存在哪里呢?

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

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

 
 
 
 
  1. const hook = {  
  2.   // hook保存的數(shù)據(jù)  
  3.   memoizedState: null,  
  4.   // 指向下一個hook  
  5.   next: hookForB  
  6.   // 本次更新以baseState為基礎(chǔ)計(jì)算新的state  
  7.   baseState: null,  
  8.   // 本次更新開始時已有的update隊(duì)列 
  9.    baseQueue: null,  
  10.   // 本次更新需要增加的update隊(duì)列  
  11.   queue: null,  
  12. }; 

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

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

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

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

回到我們開篇第一個問題:

 
 
 
 
  1. function App() {  
  2.   const [num, updateNum] = useState(0);  
  3.   window.updateNum = updateNum;  
  4.   return num;  

調(diào)用window.updateNum(1)可以將視圖中的0更新為1么?

我們需要看看這里的updateNum方法的具體實(shí)現(xiàn):

 
 
 
 
  1. 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

第二個問題

 
 
 
 
  1. function App() {  
  2.   const [num, updateNum] = useState(0); 
  3.    function increment() {  
  4.     setTimeout(() => {  
  5.       updateNum(num + 1);  
  6.     }, 1000);  
  7.   }  
  8.   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計(jì)算新的state,并且num變量也還未變化(即5次update.action(即num + 1)為同一個值)。

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

useState與useReducer

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

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

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

但是我們可以控制action。

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

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

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

 
 
 
 
  1. let newState = baseState;  
  2. let firstUpdate = hook.baseQueue.next;  
  3. let update = firstUpdate; 
  4. // 遍歷baseQueue中的每一個update  
  5. do {  
  6.   if (typeof update.action === 'function') {  
  7.     newState = update.action(newState);  
  8.   } else {  
  9.     newState = action;  
  10.   }  
  11. } while (update !== firstUpdate) 

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

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

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

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

 
 
 
 
  1. function basicStateReducer(state, action) {  
  2.   return typeof action === 'function' ? action(state) : action;  

總結(jié)

通過本文,我們了解了useState的完整執(zhí)行過程。


分享名稱:關(guān)于useState的一切
URL標(biāo)題:http://m.5511xx.com/article/coppjpi.html