最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React超詳細(xì)分析useState與useReducer源碼

 更新時(shí)間:2022年11月04日 11:22:41   作者:goClient1992  
我正在處理的組件是表單的時(shí)間輸入。表單相對(duì)復(fù)雜,并且是動(dòng)態(tài)生成的,根據(jù)嵌套在其他數(shù)據(jù)中的數(shù)據(jù)顯示不同的字段。我正在用useReducer管理表單的狀態(tài),到目前為止效果很好

熱身準(zhǔn)備

在正式講useState,我們先熱熱身,了解下必備知識(shí)。

為什么會(huì)有hooks

大家都知道hooks是在函數(shù)組件的產(chǎn)物。之前class組件為什么沒(méi)有出現(xiàn)hooks這種東西呢?

答案很簡(jiǎn)單,不需要。

因?yàn)樵?code>class組件中,在運(yùn)行時(shí),只會(huì)生成一個(gè)實(shí)例,而在這個(gè)實(shí)例中會(huì)保存組件的state等信息。在后續(xù)的更新操作中,也只是調(diào)用其中的render方法,實(shí)例中的信息不會(huì)丟失。而在函數(shù)組件中,每次渲染,更新都會(huì)去執(zhí)行這個(gè)函數(shù)組件,所以在函數(shù)組件中是沒(méi)辦法保存state等信息的。為了保存state等信息,于是有了hooks,用來(lái)記錄函數(shù)組件的狀態(tài),執(zhí)行副作用。

hooks執(zhí)行時(shí)機(jī)

上面提到,在函數(shù)組件中,每次渲染,更新都會(huì)去執(zhí)行這個(gè)函數(shù)組件。所以我們?cè)诤瘮?shù)組件內(nèi)部聲明的hooks也會(huì)在每次執(zhí)行函數(shù)組件時(shí)執(zhí)行。

在這個(gè)時(shí)候,可能有的同學(xué)聽(tīng)了我上面的說(shuō)法(hooks用來(lái)記錄函數(shù)組件的狀態(tài),執(zhí)行副作用),又有疑惑了,既然每次函數(shù)組件執(zhí)行都會(huì)執(zhí)行hooks方法,那hooks是怎么記錄函數(shù)組件的狀態(tài)的呢?

答案是,記錄在函數(shù)組件對(duì)應(yīng)的fiber節(jié)點(diǎn)中。

兩套hooks

在我們剛開(kāi)始學(xué)習(xí)使用hooks時(shí),可能會(huì)有疑惑, 為什么hooks要在函數(shù)組件的頂部聲明,而不能在條件語(yǔ)句或內(nèi)部函數(shù)中聲明?

答案是,React維護(hù)了兩套hooks,一套用來(lái)在項(xiàng)目初始化mount時(shí),初始化hooks。而在后續(xù)的更新操作中會(huì)基于初始化的hooks執(zhí)行更新操作。如果我們?cè)跅l件語(yǔ)句或函數(shù)中聲明hooks,有可能在項(xiàng)目初始化時(shí)不會(huì)聲明,這樣就會(huì)導(dǎo)致在后面的更新操作中出問(wèn)題。

hooks存儲(chǔ)

提前講一下hooks存儲(chǔ)方式,避免看暈了~~~

每個(gè)初始化的hook都會(huì)創(chuàng)建一個(gè)hook結(jié)構(gòu),多個(gè)hook是通過(guò)聲明順序用鏈表的結(jié)構(gòu)相關(guān)聯(lián),最終這個(gè)鏈表會(huì)存放在fiber.memoizedState中:

var hook = {
    memoizedState: null,   // 存儲(chǔ)hook操作,不要和fiber.memoizedState搞混了
    baseState: null,
    baseQueue: null,
    queue: null,    // 存儲(chǔ)該hook本次更新階段的所有更新操作
    next: null      // 鏈接下一個(gè)hook
};

而在每個(gè)hook.queue中存放的么個(gè)update也是一個(gè)鏈表結(jié)構(gòu)存儲(chǔ)的,千萬(wàn)不要和hook的鏈表搞混了。

接下來(lái),讓我們帶著下面幾個(gè)問(wèn)題看文章:

  1. 為什么setState后不能馬上拿到最新的state的值?
  2. 多個(gè)setState是如何合并的?
  3. setState到底是同步還是異步的?
  4. 為什么setState的值相同時(shí),函數(shù)組件不更新?

假如我們有下面這樣一段代碼:

function App(){
  const [count, setCount] = useState(0)
  const handleClick = () => {
    setCount(count => count + 1)
  }
  return (
    <div>
        勇敢牛牛,        <span>不怕困難</span>
        <span onClick={handleClick}>{count}</span>
    </div>
  )
}

初始化 mount

useState

我們先來(lái)看下useState()函數(shù):

function useState(initialState) {
  var dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

上面的dispatcher就會(huì)涉及到開(kāi)始提到的兩套hooks的變換使用,initialState是我們傳入useState的參數(shù),可以是基礎(chǔ)數(shù)據(jù)類型,也可以是函數(shù),我們主要看dispatcher.useState(initialState)方法,因?yàn)槲覀冞@里是初始化,它會(huì)調(diào)用mountState方法:相關(guān)參考視頻:傳送門

function mountState(initialState) {
  var hook = mountWorkInProgressHook();   // workInProgressHook
  if (typeof initialState === 'function') {
    // 在這里,如果我們傳入的參數(shù)是函數(shù),會(huì)執(zhí)行拿到return作為initialState
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  var queue = hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: initialState
  };
  var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);
  return [hook.memoizedState, dispatch];
}

上面的代碼還是比較簡(jiǎn)單,主要就是根據(jù)useState()的入?yún)⑸梢粋€(gè)queue并保存在hook中,然后將入?yún)⒑徒壎藘蓚€(gè)參數(shù)的dispatchAction作為返回值暴露到函數(shù)組件中去使用。

這兩個(gè)返回值,第一個(gè)hook.memoizedState比較好理解,就是初始值,第二個(gè)dispatch,也就是dispatchAction.bind(null, currentlyRenderingFiber$1, queue)這是個(gè)什么東西呢?

我們知道使用useState()方法會(huì)返回兩個(gè)值state, setState,這個(gè)setState就對(duì)應(yīng)上面的dispatchAction,這個(gè)函數(shù)是怎么做到幫我們?cè)O(shè)置state的值的呢?

我們先保留這個(gè)疑問(wèn),往下看,在后面會(huì)慢慢揭曉答案。

接下來(lái)我們主要看看mountWorkInProgressHook都做了些什么。

mountWorkInProgressHook

function mountWorkInProgressHook() {
  var hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
  };
  // 這里的if/else主要用來(lái)區(qū)分是否是第一個(gè)hook
  if (workInProgressHook === null) {
    currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
  } else {
  //  把hook加到hooks鏈表的最后一條, 并且指針指向這條hook
    workInProgressHook = workInProgressHook.next = hook;  
  }
  return workInProgressHook;
}

從上面的currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;這一行代碼,我們可以發(fā)現(xiàn),hook是存放在對(duì)應(yīng)fiber.memoizedState上的。

workInProgressHook = workInProgressHook.next = hook; ,從這一行代碼,我們能知道,如果是有多個(gè)hook,他們是以鏈表的形式進(jìn)行的存放。

不僅僅是useState()這個(gè)hook會(huì)在初始化時(shí)走mountWorkInProgressHook方法,其他的hook,例如:useEffect, useRef, useCallback等在初始化時(shí)都是調(diào)用的這個(gè)方法。

到這里我們能搞明白兩件事:

  • hooks的狀態(tài)數(shù)據(jù)是存放在對(duì)應(yīng)的函數(shù)組件的fiber.memoizedState;
  • 一個(gè)函數(shù)組件上如果有多個(gè)hook,他們會(huì)通過(guò)聲明的順序以鏈表的結(jié)構(gòu)存儲(chǔ);

到這里,我們的useState()已經(jīng)完成了它初始化時(shí)的所有工作了,簡(jiǎn)單概括下,useState()在初始化時(shí)會(huì)將我們傳入的初始值以hook的結(jié)構(gòu)存放到對(duì)應(yīng)的fiber.memoizedState,以數(shù)組形式返回[state, dispatchAction]。

更新update

當(dāng)我們以某種形式觸發(fā)setState()時(shí),React也會(huì)根據(jù)setState()的值來(lái)決定如何更新視圖。

在上面講到,useState在初始化時(shí)會(huì)返回[state, dispatchAction],那我們調(diào)用setState()方法,實(shí)際上就是調(diào)用dispatchAction,而且這個(gè)函數(shù)在初始化時(shí)還通過(guò)bind綁定了兩個(gè)參數(shù), 一個(gè)是useState初始化時(shí)函數(shù)組件對(duì)應(yīng)的fiber,另一個(gè)是hook結(jié)構(gòu)的queue

來(lái)看下我精簡(jiǎn)后的dispatchAction(去除了和setState無(wú)關(guān)的代碼)

function dispatchAction(fiber, queue, action) {
  // 創(chuàng)建一個(gè)update,用于后續(xù)的更新,這里的action就是我們setState的入?yún)?
  var update = {
    lane: lane,
    action: action,
    eagerReducer: null,
    eagerState: null,
    next: null
  };
  // 這段閉環(huán)鏈表插入update的操作有沒(méi)有很熟悉?
  var pending = queue.pending;
  if (pending === null) {
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
  var alternate = fiber.alternate;
    // 判斷當(dāng)前是否是渲染階段
    if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
      var lastRenderedReducer = queue.lastRenderedReducer;
       // 這個(gè)if語(yǔ)句里的一大段就是用來(lái)判斷我們這次更新是否和上次一樣,如果一樣就不會(huì)在進(jìn)行調(diào)度更新
      if (lastRenderedReducer !== null) {
        var prevDispatcher;
        {
          prevDispatcher = ReactCurrentDispatcher$1.current;
          ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
        }
        try {
          var currentState = queue.lastRenderedState;
          var eagerState = lastRenderedReducer(currentState, action);
          update.eagerReducer = lastRenderedReducer;
          update.eagerState = eagerState;
          if (objectIs(eagerState, currentState)) {
            return;
          }
        } finally {
          {
            ReactCurrentDispatcher$1.current = prevDispatcher;
          }
        }
      }
    }
    // 將攜帶有update的fiber進(jìn)行調(diào)度更新
    scheduleUpdateOnFiber(fiber, lane, eventTime);
  }
}

上面的代碼已經(jīng)是我盡力精簡(jiǎn)的結(jié)果了。。。代碼上有注釋,各位看官湊合看下。

不愿細(xì)看的我來(lái)總結(jié)下dispatchAction做的事情:

  • 創(chuàng)建一個(gè)update并加入到fiber.hook.queue鏈表中,并且鏈表指針指向這個(gè)update;
  • 判斷當(dāng)前是否是渲染階段決定要不要馬上調(diào)度更新;
  • 判斷這次的操作和上次的操作是否相同, 如果相同則不進(jìn)行調(diào)度更新;
  • 滿足上述條件則將帶有updatefiber進(jìn)行調(diào)度更新;

到這里我們又搞明白了一個(gè)問(wèn)題:

為什么setState的值相同時(shí),函數(shù)組件不更新?

updateState

我們這里不詳細(xì)講解調(diào)度更新的過(guò)程, 后面文章安排, 這里我們只需要知道,在接下來(lái)更新過(guò)程中,會(huì)再次執(zhí)行我們的函數(shù)組件,這時(shí)又會(huì)調(diào)用useState方法了。前面講過(guò),React維護(hù)了兩套hooks,一套用于初始化, 一套用于更新。 這個(gè)在調(diào)度更新時(shí)就已經(jīng)完成了切換。所以我們這次調(diào)用useState方法會(huì)和之前初始化有所不同。

這次我們進(jìn)入useState,會(huì)看到其實(shí)是調(diào)用的updateState方法

function updateState(initialState) {
  return updateReducer(basicStateReducer);
}

看到這幾行代碼,看官們應(yīng)該就明白為什么網(wǎng)上有人說(shuō)useStateuseReducer相似。原來(lái)在useState的更新中調(diào)用的就是updateReducer啊。

updateReducer

本來(lái)很長(zhǎng),想讓各位看官忍一忍。于心不忍,忍痛減了很多

function updateReducer(reducer, initialArg, init) {
  // 創(chuàng)建一個(gè)新的hook,帶有dispatchAction創(chuàng)建的update
  var hook = updateWorkInProgressHook();
  var queue = hook.queue;
  queue.lastRenderedReducer = reducer;
  var current = currentHook;
  var baseQueue = current.baseQueue; 
  var pendingQueue = queue.pending;
  current.baseQueue = baseQueue = pendingQueue;
  if (baseQueue !== null) {
    // 從這里能看到之前講的創(chuàng)建閉環(huán)鏈表插入update的好處了吧?直接next就能找到第一個(gè)update
    var first = baseQueue.next;
    var newState = current.baseState;
    var update = first;
    // 開(kāi)始遍歷update鏈表執(zhí)行所有setState
    do {
      var updateLane = update.lane;
      // 假如我們這個(gè)update上有多個(gè)setState,在循環(huán)過(guò)程中,最終都會(huì)做合并操作
      var action = update.action;
      // 這里的reducer會(huì)判斷action類型,下面講
      newState = reducer(newState, action);
      update = update.next;
    } while (update !== null && update !== first);
    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;
    queue.lastRenderedState = newState;
  }
  var dispatch = queue.dispatch;
  return [hook.memoizedState, dispatch];
}

上面的更新中,會(huì)循環(huán)遍歷update進(jìn)行一個(gè)合并操作,只取最后一個(gè)setState的值,這時(shí)候可能有人會(huì)問(wèn)那直接取最后一個(gè)setState的值不是更方便嗎?

這樣做是不行的,因?yàn)?code>setState入?yún)⒖梢允腔A(chǔ)類型也可以是函數(shù), 如果傳入的是函數(shù),它會(huì)依賴上一個(gè)setState的值來(lái)完成更新操作,下面的代碼就是上面的循環(huán)中的reducer

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

到這里我們搞明白了一個(gè)問(wèn)題,多個(gè)setState是如何合并的?

updateWorkInProgressHook

下面是偽代碼,我把很多的邏輯判斷給刪除了,免了太長(zhǎng)又讓各位看官難受,原來(lái)的代碼里會(huì)判斷當(dāng)前的hook是不是第一個(gè)調(diào)度更新的hook,我這里為了簡(jiǎn)單就按第一個(gè)來(lái)解析

function updateWorkInProgressHook() {
  var nextCurrentHook;
  nextCurrentHook = current.memoizedState;
  var newHook = {
      memoizedState: currentHook.memoizedState,
      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,
      next: null
      }
  currentlyRenderingFiber$1.memoizedState = workInProgressHook = newHook;
  return workInProgressHook;
}

從上面代碼能看出來(lái),updateWorkInProgressHook拋去那些判斷, 其實(shí)做的事情也很簡(jiǎn)單,就是基于fiber.memoizedState創(chuàng)建一個(gè)新的hook結(jié)構(gòu)覆蓋之前的hook。前面dispatchAction講到會(huì)把update加入到hook.queue中,在這里的newHook.queue上就有這個(gè)update。

總結(jié)

總結(jié)下useState初始化和setState更新:

  1. useState會(huì)在第一次執(zhí)行函數(shù)組件時(shí)進(jìn)行初始化,返回[state, dispatchAction]。
  2. 當(dāng)我們通過(guò)setState也就是dispatchAction進(jìn)行調(diào)度更新時(shí),會(huì)創(chuàng)建一個(gè)update加入到hook.queue中。
  3. 當(dāng)更新過(guò)程中再次執(zhí)行函數(shù)組件,也會(huì)調(diào)用useState方法,此時(shí)的useState內(nèi)部會(huì)使用更新時(shí)的hooks。
  4. 通過(guò)updateWorkInProgressHook獲取到dispatchAction創(chuàng)建的update
  5. updateReducer通過(guò)遍歷update鏈表完成setState合并。
  6. 返回update后的[newState, dispatchAction].

還有兩個(gè)問(wèn)題

為什么setState后不能馬上拿到最新的state的值? React其實(shí)可以這么做,為什么沒(méi)有這么做,因?yàn)槊總€(gè)setState都會(huì)觸發(fā)更新,React出于性能考慮,會(huì)做一個(gè)合并操作。所以setState只是觸發(fā)了dispatchAction生成了一個(gè)update的動(dòng)作,新的state會(huì)存儲(chǔ)在update中,等到下一次render, 觸發(fā)這個(gè)useState所在的函數(shù)組件執(zhí)行,才會(huì)賦值新的state。

setState到底是同步還是異步的?

同步的,假如我們有這樣一段代碼:

const handleClick = () => {
  setCount(2)
  setCount(count => count + 1)
  console.log('after setCount')
}

你會(huì)驚奇的發(fā)現(xiàn)頁(yè)面還沒(méi)有更新count,但是控制臺(tái)已經(jīng)打印了after setCount。

之所以表現(xiàn)上像是異步,是因?yàn)閮?nèi)部使用了try{...}finally{...}。當(dāng)調(diào)用setState觸發(fā)調(diào)度更新時(shí),更新操作會(huì)放在finally中,返回去繼續(xù)執(zhí)行handlelick的邏輯。于是會(huì)出現(xiàn)上面的情況。

看完這篇文章, 我們可以弄明白下面這幾個(gè)問(wèn)題:

  1. 為什么setState后不能馬上拿到最新的state的值?
  2. 多個(gè)setState是如何合并的?
  3. setState到底是同步還是異步的?
  4. 為什么setState的值相同時(shí),函數(shù)組件不更新?
  5. setState是怎么完成更新的?
  6. useState是什么時(shí)候初始化又是什么時(shí)候開(kāi)始更新的?

到此這篇關(guān)于React超詳細(xì)分析useState與useReducer源碼的文章就介紹到這了,更多相關(guān)React useState與useReducer內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React中redux的使用詳解

    React中redux的使用詳解

    Redux 是一個(gè)狀態(tài)管理庫(kù),它可以幫助你管理應(yīng)用程序中的所有狀態(tài),Redux的核心概念之一是Store,它表示整個(gè)應(yīng)用程序的狀態(tài),這篇文章給大家介紹React中redux的使用,感興趣的朋友一起看看吧
    2023-12-12
  • react中關(guān)于useCallback的用法

    react中關(guān)于useCallback的用法

    這篇文章主要介紹了react中關(guān)于useCallback的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • antd踩坑之javascriptEnabled配置方式

    antd踩坑之javascriptEnabled配置方式

    這篇文章主要介紹了antd踩坑之javascriptEnabled配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • react native帶索引的城市列表組件的實(shí)例代碼

    react native帶索引的城市列表組件的實(shí)例代碼

    本篇文章主要介紹了react-native城市列表組件的實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-08-08
  • 實(shí)現(xiàn)React單頁(yè)應(yīng)用的方法詳解

    實(shí)現(xiàn)React單頁(yè)應(yīng)用的方法詳解

    今天我們來(lái)學(xué)習(xí)React是如何構(gòu)建起一個(gè)單頁(yè)應(yīng)用的,React作為目前最流行的前端框架之一,其受歡迎程度不容小覷,從這門框架上我們可以學(xué)到許多其他前端框架所缺失的東西,也是其創(chuàng)新性所在的地方,比如虛擬DOM、JSX等。下面一起來(lái)看看。
    2016-08-08
  • 詳解React 的幾種條件渲染以及選擇

    詳解React 的幾種條件渲染以及選擇

    這篇文章主要介紹了詳解React 的幾種條件渲染以及選擇,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-10-10
  • React中使用Mobx的方法

    React中使用Mobx的方法

    Mobx是一個(gè)前端“狀態(tài)管理框架”,狀態(tài)管理就是將分布在各個(gè)組件、各個(gè)模塊中的狀態(tài)的變化,按照一定的規(guī)則,進(jìn)行統(tǒng)一的管理,這篇文章主要介紹了React中如何使用Mobx,需要的朋友可以參考下
    2023-02-02
  • React中Portals與錯(cuò)誤邊界處理實(shí)現(xiàn)

    React中Portals與錯(cuò)誤邊界處理實(shí)現(xiàn)

    本文主要介紹了React中Portals與錯(cuò)誤邊界處理實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • IntersectionObserver實(shí)現(xiàn)加載更多組件demo

    IntersectionObserver實(shí)現(xiàn)加載更多組件demo

    這篇文章主要為大家介紹了IntersectionObserver實(shí)現(xiàn)加載更多組件demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • react?context優(yōu)化四重奏教程示例

    react?context優(yōu)化四重奏教程示例

    這篇文章主要為大家介紹了react?context優(yōu)化四重奏教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10

最新評(píng)論

清徐县| 关岭| 金门县| 永登县| 阜平县| 吐鲁番市| 九江县| 平果县| 富民县| 奉节县| 浙江省| 自治县| 蓝山县| 西乡县| 通州市| 凤山市| 宁武县| 梁平县| 庆城县| 承德县| 岢岚县| 郯城县| 溧阳市| 云安县| 高陵县| 太原市| 新巴尔虎右旗| 葵青区| 林州市| 武平县| 九龙坡区| 霸州市| 读书| 淳化县| 沈丘县| 韶关市| 孙吴县| 马龙县| 汝州市| 盐池县| 威远县|