欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React渲染機(jī)制超詳細(xì)講解

 更新時(shí)間:2022年11月04日 10:59:46   作者:goClient1992  
React整個(gè)的渲染機(jī)制就是React會(huì)調(diào)用render()函數(shù)構(gòu)建一棵Dom樹,在state/props發(fā)生改變的時(shí)候,render()函數(shù)會(huì)被再次調(diào)用渲染出另外一棵樹,重新渲染所有的節(jié)點(diǎn),構(gòu)造出新的虛擬Dom tree

準(zhǔn)備工作

為了方便講解,假設(shè)我們有下面這樣一段代碼:

function App(){
  const [count, setCount] = useState(0)
  useEffect(() => {
    setCount(1)
  }, [])
  const handleClick = () => setCount(count => count++)
  return (
    <div>
        勇敢牛牛,        <span>不怕困難</span>
        <span onClick={handleClick}>{count}</span>
    </div>
  )
}
ReactDom.render(<App />, document.querySelector('#root'))

在React項(xiàng)目中,這種jsx語法首先會(huì)被編譯成:

React.createElement("App", null)
or
jsx("App", null)

這里不詳說編譯方法,感興趣的可以參考:

babel在線編譯

新的jsx轉(zhuǎn)換

jsx語法轉(zhuǎn)換后,會(huì)通過creatElementjsx的api轉(zhuǎn)換為React element作為ReactDom.render()的第一個(gè)參數(shù)進(jìn)行渲染。

在上一篇文章Fiber中,我們提到過一個(gè)React項(xiàng)目會(huì)有一個(gè)fiberRoot和一個(gè)或多個(gè)rootFiber。fiberRoot是一個(gè)項(xiàng)目的根節(jié)點(diǎn)。我們?cè)陂_始真正的渲染前會(huì)先基于rootDOM創(chuàng)建fiberRoot,且fiberRoot.current = rootFiber,這里的rootFiber就是currentfiber樹的根節(jié)點(diǎn)。

if (!root) {
    // Initial mount
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
    fiberRoot = root._internalRoot;
}

在創(chuàng)建好fiberRootrootFiber后,我們還不知道接下來要做什么,因?yàn)樗鼈兒臀覀兊?code><App />函數(shù)組件沒有一點(diǎn)關(guān)聯(lián)。這時(shí)React開始創(chuàng)建update,并將ReactDom.render()的第一個(gè)參數(shù),也就是基于<App />創(chuàng)建的React element賦給update

var update = {
    eventTime: eventTime,
    lane: lane,
    tag: UpdateState,
    payload: null,
    callback: element,
    next: null
  };

有了這個(gè)update,還需要將它加入到更新隊(duì)列中,等待后續(xù)進(jìn)行更新。在這里有必要講下這個(gè)隊(duì)列的創(chuàng)建流程,這個(gè)創(chuàng)建操作在React有多次應(yīng)用。

var sharedQueue = updateQueue.shared;
  var pending = sharedQueue.pending;
  if (pending === null) {   
  // mount時(shí)只有一個(gè)update,直接閉環(huán)
    update.next = update;
  } else {   
  // update時(shí),將最新的update的next指向上一次的update, 上一次的update的next又指向最新的update形成閉環(huán)
    update.next = pending.next;
    pending.next = update;
  }
  // pending指向最新的update, 這樣我們遍歷update鏈表時(shí), pending.next會(huì)指向第一個(gè)插入的update。
  sharedQueue.pending = update;   

我將上面的代碼進(jìn)行了一下抽象,更新隊(duì)列是一個(gè)環(huán)形鏈表結(jié)構(gòu),每次向鏈表結(jié)尾添加一個(gè)update時(shí),指針都會(huì)指向這個(gè)update,并且這個(gè)update.next會(huì)指向第一個(gè)更新:

上一篇文章也講過,React最多會(huì)同時(shí)擁有兩個(gè)fiber樹,一個(gè)是currentfiber樹,另一個(gè)是workInProgressfiber樹。currentfiber樹的根節(jié)點(diǎn)在上面已經(jīng)創(chuàng)建,下面會(huì)通過拷貝fiberRoot.current的形式創(chuàng)建workInProgressfiber樹的根節(jié)點(diǎn)。

到這里,前面的準(zhǔn)備工作就做完了, 接下來進(jìn)入正菜,開始進(jìn)行循環(huán)遍歷,生成fiber樹和dom樹,并最終渲染到頁面中。相關(guān)參考視頻講解:進(jìn)入學(xué)習(xí)

render階段

這個(gè)階段并不是指把代碼渲染到頁面上,而是基于我們的代碼畫出對(duì)應(yīng)的fiber樹和dom樹。

workloopSync

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

在這個(gè)循環(huán)里,會(huì)不斷根據(jù)workInProgress找到對(duì)應(yīng)的child作為下次循環(huán)的workInProgress,直到遍歷到葉子節(jié)點(diǎn),即深度優(yōu)先遍歷。在performUnitOfWork會(huì)執(zhí)行下面的beginWork。

beginWork

簡(jiǎn)單描述下beginWork的工作,就是生成fiber樹。

基于workInProgress的根節(jié)點(diǎn)生成<App />fiber節(jié)點(diǎn)并將這個(gè)節(jié)點(diǎn)作為根節(jié)點(diǎn)的child,然后基于<App />fiber節(jié)點(diǎn)生成<div />fiber節(jié)點(diǎn)并作為<App />fiber節(jié)點(diǎn)的child,如此循環(huán)直到最下面的牛牛文本。

注意, 在上面流程圖中,updateFunctionComponent會(huì)執(zhí)行一個(gè)renderWithHooks函數(shù),這個(gè)函數(shù)里面會(huì)執(zhí)行App()這個(gè)函數(shù)組件,在這里會(huì)初始化函數(shù)組件里所有的hooks,也就是上面實(shí)例代碼的useState()

當(dāng)遍歷到牛牛文本時(shí),它的下面已經(jīng)沒有了child,這時(shí)beginWork的工作就暫時(shí)告一段落,為什么說是暫時(shí),是因?yàn)樵?code>completeWork時(shí),如果遍歷的fiber節(jié)點(diǎn)有sibling會(huì)再次走到beginWork

completeWork

當(dāng)遍歷到牛牛文本后,會(huì)進(jìn)入這個(gè)completeWork。

在這里,我們?cè)俸?jiǎn)單描述下completeWork的工作, 就是生成dom樹。

基于fiber節(jié)點(diǎn)生成對(duì)應(yīng)的dom節(jié)點(diǎn),并且將這個(gè)dom節(jié)點(diǎn)作為父節(jié)點(diǎn),將之前生成的dom節(jié)點(diǎn)插入到當(dāng)前創(chuàng)建的dom節(jié)點(diǎn)。并會(huì)基于在beginWork生成的不完全的workInProgressfiber樹向上查找,直到fiberRoot。在這個(gè)向上的過程中,會(huì)去判斷是否有sibling,如果有會(huì)再次走beginWork,沒有就繼續(xù)向上。這樣到了根節(jié)點(diǎn),一個(gè)完整的dom樹就生成了。

額外提一下,在completeWork中有這樣一段代碼

if (flags > PerformedWork) {
  if (returnFiber.lastEffect !== null) {
    returnFiber.lastEffect.nextEffect = completedWork;
  } else {
    returnFiber.firstEffect = completedWork;
  }
  returnFiber.lastEffect = completedWork;
}

解釋一下, flags > PerformedWork代表當(dāng)前這個(gè)fiber節(jié)點(diǎn)是有副作用的,需要將這個(gè)fiber節(jié)點(diǎn)加入到父級(jí)fibereffectList鏈表中。

commit階段

這個(gè)階段的主要工作是處理副作用。所謂副作用就是不確定操作,比如:插入,替換,刪除DOM,還有useEffect()hook的回調(diào)函數(shù)都會(huì)被作為副作用。

commitWork

準(zhǔn)備工作

commitWork前,會(huì)將在workloopSync中生成的workInProgressfiber樹賦值給fiberRootfinishedWork屬性。

var finishedWork = root.current.alternate;  // workInProgress fiber樹
root.finishedWork = finishedWork;  // 這里的root是fiberRoot
root.finishedLanes = lanes;
commitRoot(root);

在上面我們提到,如果一個(gè)fiber節(jié)點(diǎn)有副作用會(huì)被記錄到父級(jí)fiberlastEffectnextEffect。

在下面代碼中,如果fiber樹有副作用,會(huì)將rootFiber.firstEffect節(jié)點(diǎn)作為第一個(gè)副作用firstEffect,并且將effectList形成閉環(huán)。

var firstEffect;
// 判斷當(dāng)前rootFiber樹是否有副作用
if (finishedWork.flags > PerformedWork) {
    // 下面代碼的目的還是為了將這個(gè)effectList鏈表形成閉環(huán)
    if (finishedWork.lastEffect !== null) {
      finishedWork.lastEffect.nextEffect = finishedWork;
      firstEffect = finishedWork.firstEffect;
    } else {
      firstEffect = finishedWork;
    }
} else {
// 這個(gè)rootFiber樹沒有副作用
firstEffect = finishedWork.firstEffect;
}

mutation之前

簡(jiǎn)單描述mutation之前階段的工作:

處理DOM節(jié)點(diǎn)渲染/刪除后的 autoFocus、blur 邏輯;

調(diào)用getSnapshotBeforeUpdate,fiberRoot和ClassComponent會(huì)走這里;

調(diào)度useEffect(異步);

在mutation之前的階段,遍歷effectList鏈表,執(zhí)行commitBeforeMutationEffects方法。

do {  // mutation之前
  invokeGuardedCallback(null, commitBeforeMutationEffects, null);
} while (nextEffect !== null);

我們進(jìn)到commitBeforeMutationEffects方法,我將代碼簡(jiǎn)化一下:

function commitBeforeMutationEffects() {
  while (nextEffect !== null) {
    var current = nextEffect.alternate;
    // 處理DOM節(jié)點(diǎn)渲染/刪除后的 autoFocus、blur 邏輯;
    if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null){...}
    var flags = nextEffect.flags;
    // 調(diào)用getSnapshotBeforeUpdate,fiberRoot和ClassComponent會(huì)走這里
    if ((flags & Snapshot) !== NoFlags) {...}
    // 調(diào)度useEffect(異步)
    if ((flags & Passive) !== NoFlags) {
      // rootDoesHavePassiveEffects變量表示當(dāng)前是否有副作用
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
        // 創(chuàng)建任務(wù)并加入任務(wù)隊(duì)列,會(huì)在layout階段之后觸發(fā)
        scheduleCallback(NormalPriority$1, function () {
          flushPassiveEffects();
          return null;
        });
      }
    }
    // 繼續(xù)遍歷下一個(gè)effect
    nextEffect = nextEffect.nextEffect;
    }
}

按照我們示例代碼,我們重點(diǎn)關(guān)注第三件事,調(diào)度useEffect(注意,這里是調(diào)度,并不會(huì)馬上執(zhí)行)。

scheduleCallback主要工作是創(chuàng)建一個(gè)task

var newTask = {
    id: taskIdCounter++,
    callback: callback,  //上面代碼傳入的回調(diào)函數(shù)
    priorityLevel: priorityLevel,
    startTime: startTime,
    expirationTime: expirationTime,
    sortIndex: -1
};

它里面有個(gè)邏輯會(huì)判斷startTimecurrentTime, 如果startTime > currentTime,會(huì)把這個(gè)任務(wù)加入到定時(shí)任務(wù)隊(duì)列timerQueue,反之會(huì)加入任務(wù)隊(duì)列taskQueue,并task.sortIndex = expirationTime。

mutation

簡(jiǎn)單描述mutation階段的工作就是負(fù)責(zé)dom渲染。

區(qū)分fiber.flags,進(jìn)行不同的操作,比如:重置文本,重置ref,插入,替換,刪除dom節(jié)點(diǎn)。

和mutation之前階段一樣,也是遍歷effectList鏈表,執(zhí)行commitMutationEffects方法。

do {    // mutation  dom渲染
  invokeGuardedCallback(null, commitMutationEffects, null, root, renderPriorityLevel);
} while (nextEffect !== null);

看下commitMutationEffects的主要工作:

function commitMutationEffects(root, renderPriorityLevel) {
  // TODO: Should probably move the bulk of this function to commitWork.
  while (nextEffect !== null) {     // 遍歷EffectList
    setCurrentFiber(nextEffect);
    // 根據(jù)flags分別處理
    var flags = nextEffect.flags;
    // 根據(jù) ContentReset flags重置文字節(jié)點(diǎn)
    if (flags & ContentReset) {...}
    // 更新ref
    if (flags & Ref) {...}
    var primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
    switch (primaryFlags) {
      case Placement:   // 插入dom
        {...}
      case PlacementAndUpdate:    //插入dom并更新dom
        {
          // Placement
          commitPlacement(nextEffect);
          nextEffect.flags &= ~Placement; // Update
          var _current = nextEffect.alternate;
          commitWork(_current, nextEffect);
          break;
        }
      case Hydrating:     //SSR
        {...}
      case HydratingAndUpdate:      // SSR
        {...}
      case Update:      // 更新dom
        {...}
      case Deletion:    // 刪除dom
        {...}
    }
    resetCurrentFiber();
    nextEffect = nextEffect.nextEffect;
  }
}

按照我們的示例代碼,這里會(huì)走PlacementAndUpdate,首先是commitPlacement(nextEffect)方法,在一串判斷后,最后會(huì)把我們生成的dom樹插入到rootDOM節(jié)點(diǎn)中。

function appendChildToContainer(container, child) {
  var parentNode;
  if (container.nodeType === COMMENT_NODE) {
    parentNode = container.parentNode;
    parentNode.insertBefore(child, container);
  } else {
    parentNode = container;
    parentNode.appendChild(child);    // 直接將整個(gè)dom作為子節(jié)點(diǎn)插入到root中
  }
}

到這里,代碼終于真正的渲染到了頁面上。下面的commitWork方法是執(zhí)行和useLayoutEffect()有關(guān)的東西,這里不做重點(diǎn),后面文章安排,我們只要知道這里是執(zhí)行上一次更新的effect unmount。

fiber樹切換

在講layout階段之前,先來看下這行代碼

root.current = finishedWork  // 將`workInProgress`fiber樹變成`current`樹

這行代碼在mutation和layout階段之間。在mutation階段, 此時(shí)的currentfiber樹還是指向更新前的fiber樹, 這樣在生命周期鉤子內(nèi)獲取的DOM就是更新前的, 類似于componentDidMountcompentDidUpdate的鉤子是在layout階段執(zhí)行的,這樣就能獲取到更新后的DOM進(jìn)行操作。

layout

簡(jiǎn)單描述layout階段的工作:

  • 調(diào)用生命周期或hooks相關(guān)操作
  • 賦值ref

和mutation之前階段一樣,也是遍歷effectList鏈表,執(zhí)行commitLayoutEffects方法。

do {   // 調(diào)用生命周期和hook相關(guān)操作, 賦值ref
   invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
} while (nextEffect !== null);

來看下commitLayoutEffects方法:

function commitLayoutEffects(root, committedLanes) {
  while (nextEffect !== null) {
    setCurrentFiber(nextEffect);
    var flags = nextEffect.flags;
    // 調(diào)用生命周期或鉤子函數(shù)
    if (flags & (Update | Callback)) {
      var current = nextEffect.alternate;
      commitLifeCycles(root, current, nextEffect);
    }
    {
      // 獲取dom實(shí)例,更新ref
      if (flags & Ref) {
        commitAttachRef(nextEffect);
      }
    }
    resetCurrentFiber();
    nextEffect = nextEffect.nextEffect;
  }
}

提一下,useLayoutEffect()的回調(diào)會(huì)在commitLifeCycles方法中執(zhí)行,而useEffect()的回調(diào)會(huì)在commitLifeCycles中的schedulePassiveEffects方法進(jìn)行調(diào)度。從這里就可以看出useLayoutEffect()useEffect()的區(qū)別:

  • useLayoutEffect的上次更新銷毀函數(shù)在mutation階段銷毀,本次更新回調(diào)函數(shù)是在dom渲染后的layout階段同步執(zhí)行;
  • useEffectmutation之前階段會(huì)創(chuàng)建調(diào)度任務(wù),在layout階段會(huì)將銷毀函數(shù)和回調(diào)函數(shù)加入到pendingPassiveHookEffectsUnmountpendingPassiveHookEffectsMount隊(duì)列中,最終它的上次更新銷毀函數(shù)和本次更新回調(diào)函數(shù)都是在layout階段后異步執(zhí)行; 可以明確一點(diǎn),他們的更新都不會(huì)阻塞dom渲染。

layout之后

還記得在mutation之前階段的這幾行代碼嗎?

// 創(chuàng)建任務(wù)并加入任務(wù)隊(duì)列,會(huì)在layout階段之后觸發(fā)
scheduleCallback(NormalPriority$1, function () {
  flushPassiveEffects();
  return null;
});

這里就是在調(diào)度useEffect(),在layout階段之后會(huì)執(zhí)行這個(gè)回調(diào)函數(shù),此時(shí)會(huì)處理useEffect的上次更新銷毀函數(shù)和本次更新回調(diào)函數(shù)。

總結(jié)

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

  1. React的渲染流程是怎樣的?
  2. React的beginWork都做了什么?
  3. React的completeWork都做了什么?
  4. React的commitWork都做了什么?
  5. useEffect和useLayoutEffect的區(qū)別是什么?
  6. useEffect和useLayoutEffect的銷毀函數(shù)和更新回調(diào)的調(diào)用時(shí)機(jī)?

到此這篇關(guān)于React渲染機(jī)制超詳細(xì)講解的文章就介紹到這了,更多相關(guān)React渲染機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React?createRef循環(huán)動(dòng)態(tài)賦值ref問題

    React?createRef循環(huán)動(dòng)態(tài)賦值ref問題

    這篇文章主要介紹了React?createRef循環(huán)動(dòng)態(tài)賦值ref問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • forwardRef?中React父組件控制子組件的實(shí)現(xiàn)代碼

    forwardRef?中React父組件控制子組件的實(shí)現(xiàn)代碼

    forwardRef 用于拿到父組件傳入的 ref 屬性,這樣在父組件便能通過 ref 控制子組件,這篇文章主要介紹了forwardRef?-?React父組件控制子組件的實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2024-01-01
  • React處理復(fù)雜圖片樣式的方法詳解

    React處理復(fù)雜圖片樣式的方法詳解

    這篇文章主要為大家詳細(xì)介紹了React處理復(fù)雜圖片樣式的方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-04-04
  • React 源碼中的依賴注入方法

    React 源碼中的依賴注入方法

    這篇文章主要介紹了React 源碼中的依賴注入方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-11-11
  • 淺談React原生APP更新

    淺談React原生APP更新

    當(dāng)一個(gè)APP在運(yùn)行的時(shí)候, 開發(fā)者想要將自己的代碼更新到用戶的手機(jī)上時(shí), 一般都有兩種方案, 一是熱更新, 二就是APP更新.熱更新暫且不說,這篇文章就講講 APP 如何更新。
    2021-06-06
  • react-router實(shí)現(xiàn)跳轉(zhuǎn)傳值的方法示例

    react-router實(shí)現(xiàn)跳轉(zhuǎn)傳值的方法示例

    這篇文章主要給大家介紹了關(guān)于react-router實(shí)現(xiàn)跳轉(zhuǎn)傳值的相關(guān)資料,文中給出了詳細(xì)的示例代碼,對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來學(xué)習(xí)學(xué)習(xí)吧。
    2017-05-05
  • react項(xiàng)目實(shí)踐之webpack-dev-serve

    react項(xiàng)目實(shí)踐之webpack-dev-serve

    這篇文章主要介紹了react項(xiàng)目實(shí)踐之webpack-dev-serve,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-09-09
  • create-react-app使用antd按需加載的樣式無效問題的解決

    create-react-app使用antd按需加載的樣式無效問題的解決

    這篇文章主要介紹了create-react-app使用antd按需加載的樣式無效問題的解決,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2019-02-02
  • 詳解在React中跨組件分發(fā)狀態(tài)的三種方法

    詳解在React中跨組件分發(fā)狀態(tài)的三種方法

    這篇文章主要介紹了詳解在React中跨組件分發(fā)狀態(tài)的三種方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-08-08
  • react-native ListView下拉刷新上拉加載實(shí)現(xiàn)代碼

    react-native ListView下拉刷新上拉加載實(shí)現(xiàn)代碼

    本篇文章主要介紹了react-native ListView下拉刷新上拉加載實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-08-08

最新評(píng)論