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

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)。我們在開始真正的渲染前會(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è)階段并不是指把代碼渲染到頁面上,而是基于我們的代碼畫出對應(yīng)的fiber樹和dom樹。

workloopSync

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

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

beginWork

簡單描述下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

在這里,我們再簡單描述下completeWork的工作, 就是生成dom樹。

基于fiber節(jié)點(diǎn)生成對應(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)加入到父級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ì)被記錄到父級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之前

簡單描述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方法,我將代碼簡化一下:

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

簡單描述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

簡單描述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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • react實(shí)現(xiàn)頁面水印效果的全過程

    react實(shí)現(xiàn)頁面水印效果的全過程

    大家常常關(guān)注的是網(wǎng)站圖片增加水印,而很少關(guān)注頁面水印,其實(shí)這個(gè)需求也是比較常見的,比如公文系統(tǒng)、合同系統(tǒng)等,這篇文章主要給大家介紹了關(guān)于react實(shí)現(xiàn)頁面水印效果的相關(guān)資料,需要的朋友可以參考下
    2021-09-09
  • react-native-fs實(shí)現(xiàn)文件下載、文本存儲(chǔ)的示例代碼

    react-native-fs實(shí)現(xiàn)文件下載、文本存儲(chǔ)的示例代碼

    本篇文章主要介紹了react-native-fs實(shí)現(xiàn)文件下載、文本存儲(chǔ)的示例代碼,具有一定的參考價(jià)值,有興趣的可以了解下
    2017-09-09
  • Ant Design與Ant Design pro入門使用教程

    Ant Design與Ant Design pro入門使用教程

    Ant Design 是一個(gè)服務(wù)于企業(yè)級產(chǎn)品的設(shè)計(jì)體系,組件庫是它的 React 實(shí)現(xiàn),antd 被發(fā)布為一個(gè) npm 包方便開發(fā)者安裝并使用,這篇文章主要介紹了Ant Design與Ant Design pro入門,需要的朋友可以參考下
    2023-12-12
  • React 條件渲染最佳實(shí)踐小結(jié)(7種)

    React 條件渲染最佳實(shí)踐小結(jié)(7種)

    這篇文章主要介紹了React 條件渲染最佳實(shí)踐小結(jié)(7種),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • React+JWT登錄界面實(shí)現(xiàn)的完整流程

    React+JWT登錄界面實(shí)現(xiàn)的完整流程

    React是一個(gè)用于渲染用戶界面的JavaScript工具庫,UI是由類似按鈕、文本框以及圖片之類的小零件構(gòu)成的,這篇文章主要介紹了React+JWT登錄界面實(shí)現(xiàn)的完整流程,需要的朋友可以參考下
    2026-04-04
  • React中路由的參數(shù)傳遞路由的配置文件詳解

    React中路由的參數(shù)傳遞路由的配置文件詳解

    路由的配置文件目前我們所有的路由定義都是直接使用Route組件,并且添加屬性來完成的,路由的參數(shù)傳遞有二種方式這,兩種方式在Router6.x中都是提供的hook函數(shù)的API,?類組件需要通過高階組件的方式使用,本文通過示例代碼詳解講解,需要的朋友參考下吧
    2022-11-11
  • React 應(yīng)用中的 CSS 鍵盤記錄器攻擊問題記錄

    React 應(yīng)用中的 CSS 鍵盤記錄器攻擊問題記錄

    React應(yīng)用中CSS鍵盤記錄器攻擊利用CSS選擇器與受控組件機(jī)制竊取密碼,通過觸發(fā)字符對應(yīng)的background-image請求,防御需限制第三方CSS、采用非受控組件及CSP/SRI措施,本文介紹React 應(yīng)用中的 CSS 鍵盤記錄器攻擊問題記錄,感興趣的朋友一起看看吧
    2025-07-07
  • 基于React實(shí)現(xiàn)一個(gè)todo打勾效果

    基于React實(shí)現(xiàn)一個(gè)todo打勾效果

    這篇文章主要為大家詳細(xì)介紹了如何基于React實(shí)現(xiàn)一個(gè)todo打勾效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-03-03
  • React Native自定義Android的SSL證書鏈校驗(yàn)

    React Native自定義Android的SSL證書鏈校驗(yàn)

    這篇文章主要為大家介紹了React Native自定義Android的SSL證書鏈校驗(yàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • react-native 完整實(shí)現(xiàn)登錄功能的示例代碼

    react-native 完整實(shí)現(xiàn)登錄功能的示例代碼

    本篇文章主要介紹了react-native 完整實(shí)現(xiàn)登錄功能的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-09-09

最新評論

洪泽县| 灵武市| 云霄县| 富川| 栖霞市| 福州市| 神农架林区| 宜兴市| 漳平市| 闽侯县| 满城县| 临清市| 陵水| 隆安县| 哈巴河县| 石河子市| 广宁县| 兴宁市| 盘山县| 宜兰县| 新乡市| 茌平县| 西和县| 社会| 托克逊县| 宁乡县| 嘉义市| 泰安市| 遂平县| 东乌珠穆沁旗| 锡林郭勒盟| 措美县| 义乌市| 阿巴嘎旗| 克拉玛依市| 吉安市| 西平县| 波密县| 体育| 杭锦旗| 运城市|