亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

React首次渲染流程是什么

發布時間:2023-03-27 13:54:50 來源:億速云 閱讀:166 作者:iii 欄目:開發技術

本篇內容介紹了“React首次渲染流程是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

    題目

    在開始進行源碼分析前,我們先來看幾個題目:

    題目一:

    渲染下面的組件,打印順序是什么?

    import React from 'react'
    const channel = new MessageChannel()
    // onmessage 是一個宏任務
    channel.port1.onmessage = () => {
      console.log('1 message channel')
    }
    export default function App() {
      React.useEffect(() => {
        console.log('2 use effect')
      }, [])
      Promise.resolve().then(() => {
        console.log('3 promise')
      })
      React.useLayoutEffect(() => {
        console.log('4 use layout effect')
        channel.port2.postMessage('')
      }, [])
      return <div>App</div>
    }

    答案:4 3 2 1

    題目二:

    點擊 p 標簽后,下面事件發生的順序

    • 頁面顯示 xingzhi

    • console.log('useLayoutEffect ayou')

    • console.log('useLayoutEffect xingzhi')

    • console.log('useEffect ayou')

    • console.log('useEffect xingzhi')

    import React from 'react'
    import {useState} from 'react'
    function Name({name}) {
      React.useEffect(() => {
        console.log(`useEffect ${name}`)
        return () => {
          console.log(`useEffect destroy ${name}`)
        }
      }, [name])
      React.useLayoutEffect(() => {
        console.log(`useLayoutEffect ${name}`)
        return () => {
          console.log(`useLayoutEffect destroy ${name}`)
        }
      }, [name])
      return <span>{name}</span>
    }
    // 點擊后,下面事件發生的順序
    // 1. 頁面顯示 xingzhi
    // 2. console.log('useLayoutEffect ayou')
    // 3. console.log('useLayoutEffect xingzhi')
    // 4. console.log('useEffect ayou')
    // 5. console.log('useEffect xingzhi')
    export default function App() {
      const [name, setName] = useState('ayou')
      const onClick = React.useCallback(() => setName('xingzhi'), [])
      return (
        <div>
          <Name name={name} />
          <p onClick={onClick}>I am 18</p>
        </div>
      )
    }

    答案:1 2 3 4 5

    你是不是都答對了呢?

    首次渲染流程

    我們以下面這個例子來闡述下首次渲染的流程:

    function Name({name}) {
      React.useEffect(() => {
        console.log(`useEffect ${name}`)
        return () => {
          console.log('useEffect destroy')
        }
      }, [name])
      React.useLayoutEffect(() => {
        console.log(`useLayoutEffect ${name}`)
        return () => {
          console.log('useLayoutEffect destroy')
        }
      }, [name])
      return <span>{name}</span>
    }
    function Gender() {
      return <i>Male</i>
    }
    export default function App() {
      const [name, setName] = useState('ayou')
      return (
        <div>
          <Name name={name} />
          <p onClick={() => setName('xingzhi')}>I am 18</p>
          <Gender />
        </div>
      )
    }
    ...
    ReactDOM.render(<App />, document.getElementById('root'))

    首先,我們看看 render,它是從 ReactDOMLegacy 中導出的,并最后調用了 legacyRenderSubtreeIntoContainer

    function legacyRenderSubtreeIntoContainer(
      parentComponent: ?React$Component<any, any>,
      children: ReactNodeList,
      container: Container,
      forceHydrate: boolean,
      callback: ?Function
    ) {
      // TODO: Without `any` type, Flow says "Property cannot be accessed on any
      // member of intersection type." Whyyyyyy.
      let root: RootType = (container._reactRootContainer: any)
      let fiberRoot
      if (!root) {
        // 首次渲染
        root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
          container,
          forceHydrate
        )
        fiberRoot = root._internalRoot
        if (typeof callback === 'function') {
          const originalCallback = callback
          callback = function () {
            const instance = getPublicRootInstance(fiberRoot)
            originalCallback.call(instance)
          }
        }
        // Initial mount should not be batched.
        unbatchedUpdates(() => {
          updateContainer(children, fiberRoot, parentComponent, callback)
        })
      } else {
        // 更新
        fiberRoot = root._internalRoot
        if (typeof callback === 'function') {
          const originalCallback = callback
          callback = function () {
            const instance = getPublicRootInstance(fiberRoot)
            originalCallback.call(instance)
          }
        }
        updateContainer(children, fiberRoot, parentComponent, callback)
      }
      return getPublicRootInstance(fiberRoot)
    }

    首次渲染時,經過下面這一系列的操作,會初始化一些東西:

    ReactDOMLegacy.js
    function legacyCreateRootFromDOMContainer(
      container: Container,
      forceHydrate: boolean
    ): RootType {
      ...
      return createLegacyRoot(
        container,
        shouldHydrate
          ? {
              hydrate: true,
            }
          : undefined
      )
    }
    ReactDOMRoot.js
    function createLegacyRoot(
      container: Container,
      options?: RootOptions,
    ): RootType {
      return new ReactDOMBlockingRoot(container, LegacyRoot, options);
    }
    function ReactDOMBlockingRoot(
      container: Container,
      tag: RootTag,
      options: void | RootOptions,
    ) {
      this._internalRoot = createRootImpl(container, tag, options);
    }
    function createRootImpl(
      container: Container,
      tag: RootTag,
      options: void | RootOptions,
    ) {
      ...
      const root = createContainer(container, tag, hydrate, hydrationCallbacks)
      ...
    }
    ReactFiberReconciler.old.js
    function createContainer(
      containerInfo: Container,
      tag: RootTag,
      hydrate: boolean,
      hydrationCallbacks: null | SuspenseHydrationCallbacks,
    ): OpaqueRoot {
      return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
    }
    ReactFiberRoot.old.js
    function createFiberRoot(
      containerInfo: any,
      tag: RootTag,
      hydrate: boolean,
      hydrationCallbacks: null | SuspenseHydrationCallbacks,
    ): FiberRoot {
      ...
      const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any)
      const uninitializedFiber = createHostRootFiber(tag)
      root.current = uninitializedFiber
      uninitializedFiber.stateNode = root
      initializeUpdateQueue(uninitializedFiber)
      return root
    }

    經過這一系列的操作以后,會形成如下的數據結構:

    React首次渲染流程是什么

    然后,會來到:

    unbatchedUpdates(() => {
      // 這里的 children 是 App 對應的這個 ReactElement
      updateContainer(children, fiberRoot, parentComponent, callback)
    })

    這里 unbatchedUpdates 會設置當前的 executionContext

    export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
      const prevExecutionContext = executionContext
      // 去掉 BatchedContext
      executionContext &= ~BatchedContext
      // 加上 LegacyUnbatchedContext
      executionContext |= LegacyUnbatchedContext
      try {
        return fn(a)
      } finally {
        executionContext = prevExecutionContext
        if (executionContext === NoContext) {
          // Flush the immediate callbacks that were scheduled during this batch
          flushSyncCallbackQueue()
        }
      }
    }

    然后執行 updateContainer

    export function updateContainer(
      element: ReactNodeList,
      container: OpaqueRoot,
      parentComponent: ?React$Component<any, any>,
      callback: ?Function
    ): ExpirationTime {
      const current = container.current
      const currentTime = requestCurrentTimeForUpdate()
      const suspenseConfig = requestCurrentSuspenseConfig()
      const expirationTime = computeExpirationForFiber(
        currentTime,
        current,
        suspenseConfig
      )
      const context = getContextForSubtree(parentComponent)
      if (container.context === null) {
        container.context = context
      } else {
        container.pendingContext = context
      }
      const update = createUpdate(expirationTime, suspenseConfig)
      // Caution: React DevTools currently depends on this property
      // being called "element".
      update.payload = {element}
      callback = callback === undefined ? null : callback
      if (callback !== null) {
        update.callback = callback
      }
      enqueueUpdate(current, update)
      scheduleUpdateOnFiber(current, expirationTime)
      return expirationTime
    }

    這里,會創建一個 update,然后入隊,我們的數據結構會變成這樣:

    React首次渲染流程是什么

    接下來就到了 scheduleUpdateOnFiber:

    export function scheduleUpdateOnFiber(
      fiber: Fiber,
      expirationTime: ExpirationTime
    ) {
      checkForNestedUpdates()
      warnAboutRenderPhaseUpdatesInDEV(fiber)
      const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime)
      if (root === null) {
        warnAboutUpdateOnUnmountedFiberInDEV(fiber)
        return
      }
      // TODO: computeExpirationForFiber also reads the priority. Pass the
      // priority as an argument to that function and this one.
      const priorityLevel = getCurrentPriorityLevel()
      if (expirationTime === Sync) {
        if (
          // Check if we're inside unbatchedUpdates
          (executionContext & LegacyUnbatchedContext) !== NoContext &&
          // Check if we're not already rendering
          (executionContext & (RenderContext | CommitContext)) === NoContext
        ) {
          // Register pending interactions on the root to avoid losing traced interaction data.
          schedulePendingInteractions(root, expirationTime)
          // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
          // root inside of batchedUpdates should be synchronous, but layout updates
          // should be deferred until the end of the batch.
          performSyncWorkOnRoot(root)
        } else {
          // 暫時不看
        }
      } else {
        // 暫時不看
      }
    }

    最后走到了 performSyncWorkOnRoot

    function performSyncWorkOnRoot(root) {
      invariant(
        (executionContext &amp; (RenderContext | CommitContext)) === NoContext,
        'Should not already be working.'
      )
      flushPassiveEffects()
      const lastExpiredTime = root.lastExpiredTime
      let expirationTime
      if (lastExpiredTime !== NoWork) {
        ...
      } else {
        // There's no expired work. This must be a new, synchronous render.
        expirationTime = Sync
      }
      let exitStatus = renderRootSync(root, expirationTime)
      ...
      const finishedWork: Fiber = (root.current.alternate: any);
      root.finishedWork = finishedWork;
      root.finishedExpirationTime = expirationTime;
      root.nextKnownPendingLevel = getRemainingExpirationTime(finishedWork);
      commitRoot(root);
      return null
    }

    這里,可以分為兩個大的步驟:

    • render

    • commit

    render

    首先看看 renderRootSync

    function renderRootSync(root, expirationTime) {
      const prevExecutionContext = executionContext
      executionContext |= RenderContext
      const prevDispatcher = pushDispatcher(root)
      // If the root or expiration time have changed, throw out the existing stack
      // and prepare a fresh one. Otherwise we'll continue where we left off.
      if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
        // 主要是給 workInProgress 賦值
        prepareFreshStack(root, expirationTime)
        startWorkOnPendingInteractions(root, expirationTime)
      }
      const prevInteractions = pushInteractions(root)
      do {
        try {
          workLoopSync()
          break
        } catch (thrownValue) {
          handleError(root, thrownValue)
        }
      } while (true)
      resetContextDependencies()
      if (enableSchedulerTracing) {
        popInteractions(((prevInteractions: any): Set&lt;Interaction&gt;))
      }
      executionContext = prevExecutionContext
      popDispatcher(prevDispatcher)
      if (workInProgress !== null) {
        // This is a sync render, so we should have finished the whole tree.
        invariant(
          false,
          'Cannot commit an incomplete root. This error is likely caused by a ' +
            'bug in React. Please file an issue.'
        )
      }
      // Set this to null to indicate there's no in-progress render.
      workInProgressRoot = null
      return workInProgressRootExitStatus
    }

    這里首先調用 prepareFreshStack(root, expirationTime),這一句主要是通過 root.current 來創建 workInProgress。調用后,數據結構成了這樣:

    React首次渲染流程是什么

    跳過中間的一些語句,我們來到 workLoopSync

    function workLoopSync() {
      // Already timed out, so perform work without checking if we need to yield.
      while (workInProgress !== null) {
        performUnitOfWork(workInProgress)
      }
    }
    function performUnitOfWork(unitOfWork: Fiber): void {
      // The current, flushed, state of this fiber is the alternate. Ideally
      // nothing should rely on this, but relying on it here means that we don't
      // need an additional field on the work in progress.
      const current = unitOfWork.alternate
      setCurrentDebugFiberInDEV(unitOfWork)
      let next
      if (enableProfilerTimer &amp;&amp; (unitOfWork.mode &amp; ProfileMode) !== NoMode) {
        startProfilerTimer(unitOfWork)
        next = beginWork(current, unitOfWork, renderExpirationTime)
        stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true)
      } else {
        next = beginWork(current, unitOfWork, renderExpirationTime)
      }
      resetCurrentDebugFiberInDEV()
      unitOfWork.memoizedProps = unitOfWork.pendingProps
      if (next === null) {
        // If this doesn't spawn new work, complete the current work.
        completeUnitOfWork(unitOfWork)
      } else {
        workInProgress = next
      }
      ReactCurrentOwner.current = null
    }

    這里又分為兩個步驟:

    • beginWork,傳入當前 Fiber 節點,創建子 Fiber 節點。

    • completeUnitOfWork,通過 Fiber 節點創建真實 DOM 節點。

    這兩個步驟會交替的執行,其目標是:

    • 構建出新的 Fiber 樹

    • 與舊 Fiber 比較得到 effect 鏈表(插入、更新、刪除、useEffect 等都會產生 effect)

    beginWork
    function beginWork(
      current: Fiber | null,
      workInProgress: Fiber,
      renderExpirationTime: ExpirationTime
    ): Fiber | null {
      const updateExpirationTime = workInProgress.expirationTime
      if (current !== null) {
        const oldProps = current.memoizedProps
        const newProps = workInProgress.pendingProps
        if (
          oldProps !== newProps ||
          hasLegacyContextChanged() ||
          // Force a re-render if the implementation changed due to hot reload:
          (__DEV__ ? workInProgress.type !== current.type : false)
        ) {
          // 略
        } else if (updateExpirationTime &lt; renderExpirationTime) {
          // 略
        } else {
          // An update was scheduled on this fiber, but there are no new props
          // nor legacy context. Set this to false. If an update queue or context
          // consumer produces a changed value, it will set this to true. Otherwise,
          // the component will assume the children have not changed and bail out.
          didReceiveUpdate = false
        }
      } else {
        didReceiveUpdate = false
      }
      // Before entering the begin phase, clear pending update priority.
      // TODO: This assumes that we're about to evaluate the component and process
      // the update queue. However, there's an exception: SimpleMemoComponent
      // sometimes bails out later in the begin phase. This indicates that we should
      // move this assignment out of the common path and into each branch.
      workInProgress.expirationTime = NoWork
      switch (workInProgress.tag) {
        case IndeterminateComponent:
        // ...省略
        case LazyComponent:
        // ...省略
        case FunctionComponent:
        // ...省略
        case ClassComponent:
        // ...省略
        case HostRoot:
          return updateHostRoot(current, workInProgress, renderExpirationTime)
        case HostComponent:
        // ...省略
        case HostText:
        // ...省略
        // ...省略其他類型
      }
    }

    這里因為是 rootFiber,所以會走到 updateHostRoot

    function updateHostRoot(current, workInProgress, renderExpirationTime) {
      // 暫時不看
      pushHostRootContext(workInProgress)
      const updateQueue = workInProgress.updateQueue
      const nextProps = workInProgress.pendingProps
      const prevState = workInProgress.memoizedState
      const prevChildren = prevState !== null ? prevState.element : null
      cloneUpdateQueue(current, workInProgress)
      processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime)
      const nextState = workInProgress.memoizedState
      // Caution: React DevTools currently depends on this property
      // being called "element".
      const nextChildren = nextState.element
      if (nextChildren === prevChildren) {
        // 省略
      }
      const root: FiberRoot = workInProgress.stateNode
      if (root.hydrate &amp;&amp; enterHydrationState(workInProgress)) {
        // 省略
      } else {
        // 給 rootFiber 生成子 fiber
        reconcileChildren(
          current,
          workInProgress,
          nextChildren,
          renderExpirationTime
        )
        resetHydrationState()
      }
      return workInProgress.child
    }

    經過 updateHostRoot 后,會返回 workInProgress.child 作為下一個 workInProgress,最后的數據結構如下(這里先忽略 reconcileChildren 這個比較復雜的函數):

    React首次渲染流程是什么

    接著會繼續進行 beginWork,這次會來到 mountIndeterminateComponent (暫時忽略)。總之,經過不斷的 beginWork 后,我們會得到如下的一個結構:

    React首次渲染流程是什么

    此時 next 為空,我們會走到:

    if (next === null) {
      // If this doesn't spawn new work, complete the current work.
      completeUnitOfWork(unitOfWork)
    } else {
      ...
    }
    completeUnitOfWork
    function completeUnitOfWork(unitOfWork: Fiber): void {
      // Attempt to complete the current unit of work, then move to the next
      // sibling. If there are no more siblings, return to the parent fiber.
      let completedWork = unitOfWork
      do {
        // The current, flushed, state of this fiber is the alternate. Ideally
        // nothing should rely on this, but relying on it here means that we don't
        // need an additional field on the work in progress.
        const current = completedWork.alternate
        const returnFiber = completedWork.return
        // Check if the work completed or if something threw.
        if ((completedWork.effectTag & Incomplete) === NoEffect) {
          setCurrentDebugFiberInDEV(completedWork)
          let next
          if (
            !enableProfilerTimer ||
            (completedWork.mode & ProfileMode) === NoMode
          ) {
            next = completeWork(current, completedWork, renderExpirationTime)
          } else {
            startProfilerTimer(completedWork)
            next = completeWork(current, completedWork, renderExpirationTime)
            // Update render duration assuming we didn't error.
            stopProfilerTimerIfRunningAndRecordDelta(completedWork, false)
          }
          resetCurrentDebugFiberInDEV()
          resetChildExpirationTime(completedWork)
          if (next !== null) {
            // Completing this fiber spawned new work. Work on that next.
            workInProgress = next
            return
          }
          if (
            returnFiber !== null &&
            // Do not append effects to parents if a sibling failed to complete
            (returnFiber.effectTag & Incomplete) === NoEffect
          ) {
            // Append all the effects of the subtree and this fiber onto the effect
            // list of the parent. The completion order of the children affects the
            // side-effect order.
            if (returnFiber.firstEffect === null) {
              returnFiber.firstEffect = completedWork.firstEffect
            }
            if (completedWork.lastEffect !== null) {
              if (returnFiber.lastEffect !== null) {
                returnFiber.lastEffect.nextEffect = completedWork.firstEffect
              }
              returnFiber.lastEffect = completedWork.lastEffect
            }
            // If this fiber had side-effects, we append it AFTER the children's
            // side-effects. We can perform certain side-effects earlier if needed,
            // by doing multiple passes over the effect list. We don't want to
            // schedule our own side-effect on our own list because if end up
            // reusing children we'll schedule this effect onto itself since we're
            // at the end.
            const effectTag = completedWork.effectTag
            // Skip both NoWork and PerformedWork tags when creating the effect
            // list. PerformedWork effect is read by React DevTools but shouldn't be
            // committed.
            if (effectTag > PerformedWork) {
              if (returnFiber.lastEffect !== null) {
                returnFiber.lastEffect.nextEffect = completedWork
              } else {
                returnFiber.firstEffect = completedWork
              }
              returnFiber.lastEffect = completedWork
            }
          }
        } else {
          // This fiber did not complete because something threw. Pop values off
          // the stack without entering the complete phase. If this is a boundary,
          // capture values if possible.
          const next = unwindWork(completedWork, renderExpirationTime)
          // Because this fiber did not complete, don't reset its expiration time.
          if (
            enableProfilerTimer &&
            (completedWork.mode & ProfileMode) !== NoMode
          ) {
            // Record the render duration for the fiber that errored.
            stopProfilerTimerIfRunningAndRecordDelta(completedWork, false)
            // Include the time spent working on failed children before continuing.
            let actualDuration = completedWork.actualDuration
            let child = completedWork.child
            while (child !== null) {
              actualDuration += child.actualDuration
              child = child.sibling
            }
            completedWork.actualDuration = actualDuration
          }
          if (next !== null) {
            // If completing this work spawned new work, do that next. We'll come
            // back here again.
            // Since we're restarting, remove anything that is not a host effect
            // from the effect tag.
            next.effectTag &= HostEffectMask
            workInProgress = next
            return
          }
          if (returnFiber !== null) {
            // Mark the parent fiber as incomplete and clear its effect list.
            returnFiber.firstEffect = returnFiber.lastEffect = null
            returnFiber.effectTag |= Incomplete
          }
        }
        const siblingFiber = completedWork.sibling
        if (siblingFiber !== null) {
          // If there is more work to do in this returnFiber, do that next.
          workInProgress = siblingFiber
          return
        }
        // Otherwise, return to the parent
        completedWork = returnFiber
        // Update the next thing we're working on in case something throws.
        workInProgress = completedWork
      } while (completedWork !== null)
      // We've reached the root.
      if (workInProgressRootExitStatus === RootIncomplete) {
        workInProgressRootExitStatus = RootCompleted
      }
    }

    此時這里的 unitOfWorkspan 對應的 fiber。從函數頭部的注釋我們可以大致知道該函數的功能:

    // Attempt to complete the current unit of work, then move to the next
    // sibling. If there are no more siblings, return to the parent fiber.
    // 嘗試去完成當前的工作單元,然后處理下一個 sibling。如果沒有 sibling 了,就返回去完成父 fiber

    這里一路走下去最后會來到 completeWork 這里 :

    case HostComponent:
      ...
      // 會調用 ReactDOMComponent.js 中的 createELement 方法創建 span 標簽
      const instance = createInstance(
        type,
        newProps,
        rootContainerInstance,
        currentHostContext,
        workInProgress
      )
      // 將子元素 append 到 instance 中
      appendAllChildren(instance, workInProgress, false, false)
      workInProgress.stateNode = instance;

    執行完后,我們的結構如下所示(我們用綠色的圓來表示真實 dom):

    React首次渲染流程是什么

    此時 next 將會是 null,我們需要往上找到下一個 completedWork,即 Name,因為 Name 是一個 FunctionComponent,所以在 completeWork 中直接返回了 null。又因為它有 sibling,所以會將它的 sibling 賦值給 workInProgress,并返回對其進行 beginWork

    const siblingFiber = completedWork.sibling
    if (siblingFiber !== null) {
      // If there is more work to do in this returnFiber, do that next.
      // workInProgress 更新為 sibling
      workInProgress = siblingFiber
      // 直接返回,回到了 performUnitOfWork
      return
    }
    function performUnitOfWork(unitOfWork: Fiber): void {
      ...
      if (next === null) {
        // If this doesn't spawn new work, complete the current work.
        // 上面的代碼回到了這里
        completeUnitOfWork(unitOfWork)
      } else {
        workInProgress = next
      }
      ReactCurrentOwner.current = null
    }

    這樣 beginWorkcompleteWork 不斷交替的執行,當我們執行到 div 的時候,我們的結構如下所示:

    React首次渲染流程是什么

    之所以要額外的分析 divcomplete 過程,是因為這個例子方便我們分析 appendAllChildren

    appendAllChildren = function (
      parent: Instance,
      workInProgress: Fiber,
      needsVisibilityToggle: boolean,
      isHidden: boolean
    ) {
      // We only have the top Fiber that was created but we need recurse down its
      // children to find all the terminal nodes.
      let node = workInProgress.child
      while (node !== null) {
        if (node.tag === HostComponent || node.tag === HostText) {
          appendInitialChild(parent, node.stateNode)
        } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
          appendInitialChild(parent, node.stateNode.instance)
        } else if (node.tag === HostPortal) {
          // If we have a portal child, then we don't want to traverse
          // down its children. Instead, we'll get insertions from each child in
          // the portal directly.
        } else if (node.child !== null) {
          node.child.return = node
          node = node.child
          continue
        }
        if (node === workInProgress) {
          return
        }
        while (node.sibling === null) {
          if (node.return === null || node.return === workInProgress) {
            return
          }
          node = node.return
        }
        node.sibling.return = node.return
        node = node.sibling
      }
    }

    由于 workInProgress 指向 div 這個 fiber,他的 childName,會進入 else if (node.child !== null) 這個條件分支。然后繼續下一個循環,此時 nodespan 這個 fiber,會進入第一個分支,將 span 對應的 dom 元素插入到 parent 之中。

    這樣不停的循環,最后會執行到 if (node === workInProgress) 退出,此時所有的子元素都 append 到了 parent 之中:

    React首次渲染流程是什么

    然后繼續 beginWorkcompleteWork,最后會來到 rootFiber。不同的是,該節點的 alternate 并不為空,且該節點 tagHootRoot,所以 completeWork 時會來到這里:

    case HostRoot: {
      ...
      updateHostContainer(workInProgress);
      return null;
    }
    updateHostContainer = function (workInProgress: Fiber) {
      // Noop
    }

    看來幾乎沒有做什么事情,到這我們的 render 階段就結束了,最后的結構如下所示:

    React首次渲染流程是什么

    其中藍色表示是有 effect 的 Fiber 節點,他們組成了一個鏈表,方便 commit 過程進行遍歷。

    可以查看 render 過程動畫。

    commit

    commit 大致可分為以下過程:

    • 準備階段

    • before mutation 階段(執行 DOM 操作前)

    • mutation 階段(執行 DOM 操作)

    • 切換 Fiber Tree

    • layout 階段(執行 DOM 操作后)

    • 收尾階段

    準備階段
    do {
      // 觸發useEffect回調與其他同步任務。由于這些任務可能觸發新的渲染,所以這里要一直遍歷執行直到沒有任務
      flushPassiveEffects()
      // 暫時沒有復現出 rootWithPendingPassiveEffects !== null 的情景
      // 首次渲染 rootWithPendingPassiveEffects 為 null
    } while (rootWithPendingPassiveEffects !== null)
    // finishedWork 就是正在工作的 rootFiber
    const finishedWork = root.
    // 優先級相關暫時不看
    const expirationTime = root.finishedExpirationTime
    if (finishedWork === null) {
      return null
    }
    root.finishedWork = null
    root.finishedExpirationTime = NoWork
    root.callbackNode = null
    root.callbackExpirationTime = NoWork
    root.callbackPriority_old = NoPriority
    const remainingExpirationTimeBeforeCommit = getRemainingExpirationTime(
      finishedWork
    )
    markRootFinishedAtTime(
      root,
      expirationTime,
      remainingExpirationTimeBeforeCommit
    )
    if (rootsWithPendingDiscreteUpdates !== null) {
      const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root)
      if (
        lastDiscreteTime !== undefined &&
        remainingExpirationTimeBeforeCommit < lastDiscreteTime
      ) {
        rootsWithPendingDiscreteUpdates.delete(root)
      }
    }
    if (root === workInProgressRoot) {
      workInProgressRoot = null
      workInProgress = null
      renderExpirationTime = NoWork
    } else {
    }
    // 將effectList賦值給firstEffect
    // 由于每個fiber的effectList只包含他的子孫節點
    // 所以根節點如果有effectTag則不會被包含進來
    // 所以這里將有effectTag的根節點插入到effectList尾部
    // 這樣才能保證有effect的fiber都在effectList中
    let firstEffect
    if (finishedWork.effectTag > PerformedWork) {
      if (finishedWork.lastEffect !== null) {
        finishedWork.lastEffect.nextEffect = finishedWork
        firstEffect = finishedWork.firstEffect
      } else {
        firstEffect = finishedWork
      }
    } else {
      firstEffect = finishedWork.firstEffect
    }

    準備階段主要是確定 firstEffect,我們的例子中就是 Name 這個 fiber

    before mutation 階段
    const prevExecutionContext = executionContext
    executionContext |= CommitContext
    const prevInteractions = pushInteractions(root)
    // Reset this to null before calling lifecycles
    ReactCurrentOwner.current = null
    // The commit phase is broken into several sub-phases. We do a separate pass
    // of the effect list for each phase: all mutation effects come before all
    // layout effects, and so on.
    // The first phase a "before mutation" phase. We use this phase to read the
    // state of the host tree right before we mutate it. This is where
    // getSnapshotBeforeUpdate is called.
    focusedInstanceHandle = prepareForCommit(root.containerInfo)
    shouldFireAfterActiveInstanceBlur = false
    nextEffect = firstEffect
    do {
      if (__DEV__) {
        ...
      } else {
        try {
          commitBeforeMutationEffects()
        } catch (error) {
          invariant(nextEffect !== null, 'Should be working on an effect.')
          captureCommitPhaseError(nextEffect, error)
          nextEffect = nextEffect.nextEffect
        }
      }
    } while (nextEffect !== null)
    // We no longer need to track the active instance fiber
    focusedInstanceHandle = null
    if (enableProfilerTimer) {
      // Mark the current commit time to be shared by all Profilers in this
      // batch. This enables them to be grouped later.
      recordCommitTime()
    }

    before mutation 階段主要是調用了 commitBeforeMutationEffects 方法:

    function commitBeforeMutationEffects() {
      while (nextEffect !== null) {
        if (
          !shouldFireAfterActiveInstanceBlur &&
          focusedInstanceHandle !== null &&
          isFiberHiddenOrDeletedAndContains(nextEffect, focusedInstanceHandle)
        ) {
          shouldFireAfterActiveInstanceBlur = true
          beforeActiveInstanceBlur()
        }
        const effectTag = nextEffect.effectTag
        if ((effectTag & Snapshot) !== NoEffect) {
          setCurrentDebugFiberInDEV(nextEffect)
          const current = nextEffect.alternate
          // 調用getSnapshotBeforeUpdate
          commitBeforeMutationEffectOnFiber(current, nextEffect)
          resetCurrentDebugFiberInDEV()
        }
        if ((effectTag & Passive) !== NoEffect) {
          // If there are passive effects, schedule a callback to flush at
          // the earliest opportunity.
          if (!rootDoesHavePassiveEffects) {
            rootDoesHavePassiveEffects = true
            scheduleCallback(NormalPriority, () => {
              flushPassiveEffects()
              return null
            })
          }
        }
        nextEffect = nextEffect.nextEffect
      }
    }

    因為 NameeffectTag 包括了 Passive,所以這里會執行:

    scheduleCallback(NormalPriority, () => {
      flushPassiveEffects()
      return null
    })

    這里主要是對 useEffect 中的任務進行異步調用,最終會在下個事件循環中執行 commitPassiveHookEffects

    export function commitPassiveHookEffects(finishedWork: Fiber): void {
      if ((finishedWork.effectTag & Passive) !== NoEffect) {
        switch (finishedWork.tag) {
          case FunctionComponent:
          case ForwardRef:
          case SimpleMemoComponent:
          case Block: {
            if (
              enableProfilerTimer &&
              enableProfilerCommitHooks &&
              finishedWork.mode & ProfileMode
            ) {
              try {
                startPassiveEffectTimer();
                commitHookEffectListUnmount(
                  HookPassive | HookHasEffect,
                  finishedWork,
                );
                commitHookEffectListMount(
                  HookPassive | HookHasEffect,
                  finishedWork,
                );
              } finally {
                recordPassiveEffectDuration(finishedWork);
              }
            } else {
              commitHookEffectListUnmount(
                HookPassive | HookHasEffect,
                finishedWork,
              );
              commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
            }
            break;
          }
          default:
            break;
        }
      }
    }
    function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
      const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
      const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
      if (lastEffect !== null) {
        const firstEffect = lastEffect.next;
        let effect = firstEffect;
        do {
          if ((effect.tag & tag) === tag) {
            // Unmount
            const destroy = effect.destroy;
            effect.destroy = undefined;
            if (destroy !== undefined) {
              destroy();
            }
          }
          effect = effect.next;
        } while (effect !== firstEffect);
      }
    }
    function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
      const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
      const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
      if (lastEffect !== null) {
        const firstEffect = lastEffect.next;
        let effect = firstEffect;
        do {
          if ((effect.tag & tag) === tag) {
            // Mount
            const create = effect.create;
            effect.destroy = create();
            ...
          }
          effect = effect.next;
        } while (effect !== firstEffect);
      }
    }

    其中,commitHookEffectListUnmount 會執行 useEffect 上次渲染返回的 destroy 方法,commitHookEffectListMount 會執行 useEffect 本次渲染的 create 方法。具體到我們的例子:

    React首次渲染流程是什么

    因為是首次渲染,所以 destroy 都是 undefined,所以只會打印 useEffect ayou

    mutation 階段

    mutation 階段主要是執行了 commitMutationEffects 這個方法:

    function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
      // TODO: Should probably move the bulk of this function to commitWork.
      while (nextEffect !== null) {
        setCurrentDebugFiberInDEV(nextEffect)
        const effectTag = nextEffect.effectTag
        ...
        // The following switch statement is only concerned about placement,
        // updates, and deletions. To avoid needing to add a case for every possible
        // bitmap value, we remove the secondary effects from the effect tag and
        // switch on that value.
        const primaryEffectTag =
          effectTag & (Placement | Update | Deletion | Hydrating)
        switch (primaryEffectTag) {
         case Placement: {
            commitPlacement(nextEffect);
            // Clear the "placement" from effect tag so that we know that this is
            // inserted, before any life-cycles like componentDidMount gets called.
            // TODO: findDOMNode doesn't rely on this any more but isMounted does
            // and isMounted is deprecated anyway so we should be able to kill this.
            nextEffect.effectTag &= ~Placement;
            break;
          }
          case PlacementAndUpdate: {
            // Placement
            commitPlacement(nextEffect);
            // Clear the "placement" from effect tag so that we know that this is
            // inserted, before any life-cycles like componentDidMount gets called.
            nextEffect.effectTag &= ~Placement;
            // Update
            const current = nextEffect.alternate;
            commitWork(current, nextEffect);
            break;
          }
          case Hydrating: {
            nextEffect.effectTag &= ~Hydrating;
            break;
          }
          case HydratingAndUpdate: {
            nextEffect.effectTag &= ~Hydrating;
            // Update
            const current = nextEffect.alternate;
            commitWork(current, nextEffect);
            break;
          }
          case Update: {
            const current = nextEffect.alternate;
            commitWork(current, nextEffect);
            break;
          }
          case Deletion: {
            commitDeletion(root, nextEffect, renderPriorityLevel);
            break;
          }
        }
      }
    }

    其中,Name 會走 Update 這個分支,執行 commitWork,最終會執行到 commitHookEffectListUnmount

    function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
      const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
      const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
      if (lastEffect !== null) {
        const firstEffect = lastEffect.next;
        let effect = firstEffect;
        do {
          if ((effect.tag & tag) === tag) {
            // Unmount
            const destroy = effect.destroy;
            effect.destroy = undefined;
            if (destroy !== undefined) {
              destroy();
            }
          }
          effect = effect.next;
        } while (effect !== firstEffect);
      }
    }

    這里會同步執行 useLayoutEffect 上次渲染返回的 destroy 方法,我們的例子里是 undefined。

    App 會走到 Placement 這個分支,執行 commitPlacement,這里的主要工作是把整棵 dom 樹插入到了 <div id='root'></div> 之中。

    切換 Fiber Tree

    mutation 階段完成后,會執行:

    root.current = finishedWork

    完成后, fiberRoot 會指向 current Fiber 樹。

    React首次渲染流程是什么

    layout 階段

    對應到我們的例子,layout 階段主要是同步執行 useLayoutEffect 中的 create 函數,所以這里會打印 useLayoutEffect ayou

    題目解析

    現在,我們來分析下文章開始的兩個題目:

    題目一:

    渲染下面的組件,打印順序是什么?

    import React from 'react'
    const channel = new MessageChannel()
    // onmessage 是一個宏任務
    channel.port1.onmessage = () => {
      console.log('1 message channel')
    }
    export default function App() {
      React.useEffect(() => {
        console.log('2 use effect')
      }, [])
      Promise.resolve().then(() => {
        console.log('3 promise')
      })
      React.useLayoutEffect(() => {
        console.log('4 use layout effect')
        channel.port2.postMessage('')
      }, [])
      return <div>App</div>
    }

    解析:

    • useLayoutEffect 中的任務會跟隨渲染過程同步執行,所以先打印 4

    • Promise 對象 then 中的任務是一個微任務,所以在 4 后面執行,打印 3

    • console.log('1 message channel')console.log('2 use effect') 都會在宏任務中執行,執行順序就看誰先生成,這里 2 比 1 先,所以先打印 2,再打印 1。

    題目二:

    點擊 p 標簽后,下面事件發生的順序

    • 頁面顯示 xingzhi

    • console.log('useLayoutEffect ayou')

    • console.log('useLayoutEffect xingzhi')

    • console.log('useEffect ayou')

    • console.log('useEffect xingzhi')

    import React from 'react'
    import {useState} from 'react'
    function Name({name}) {
      React.useEffect(() => {
        console.log(`useEffect ${name}`)
        return () => {
          console.log(`useEffect destroy ${name}`)
        }
      }, [name])
      React.useLayoutEffect(() => {
        console.log(`useLayoutEffect ${name}`)
        return () => {
          console.log(`useLayoutEffect destroy ${name}`)
        }
      }, [name])
      return <span>{name}</span>
    }
    // 點擊后,下面事件發生的順序
    // 1. 頁面顯示 xingzhi
    // 2. console.log('useLayoutEffect destroy ayou')
    // 3. console.log(`useLayoutEffect xingzhi`)
    // 4. console.log('useEffect destroy ayou')
    // 5. console.log(`useEffect xingzhi`)
    export default function App() {
      const [name, setName] = useState('ayou')
      const onClick = React.useCallback(() => setName('xingzhi'), [])
      return (
        <div>
          <Name name={name} />
          <p onClick={onClick}>I am 18</p>
        </div>
      )
    }

    解析:

    • span 這個 Fiber 位于 effect 鏈表的首部,在 commitMutations 中會先處理,所以頁面先顯示 xingzhi。

    • Name 這個 Fiber 位于 span 之后,所以 useLayoutEffect 中上一次的 destroy 緊接著其執行。打印 useLayoutEffect ayou。

    • commitLayoutEffects 中執行 useLayoutEffect 這一次的 create。打印 useLayoutEffect xingzhi。

    • useEffect 在下一個宏任務中執行,先執行上一次的 destroy,再執行這一次的 create。所以先打印 useEffect ayou,再打印 useEffect xingzhi。

    “React首次渲染流程是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    新建县| 手机| 嘉峪关市| 巴林左旗| 南木林县| 保定市| 凤冈县| 茂名市| 屏山县| 高雄市| 江华| 泽普县| 电白县| 塘沽区| 韩城市| 黄冈市| 武平县| 庆阳市| 昌乐县| 大足县| 洮南市| 扶余县| 江安县| 滦南县| 淮北市| 盐边县| 镇坪县| 滦平县| 宝山区| 闸北区| 江都市| 辽阳县| 河间市| 天等县| 武汉市| 新沂市| 襄垣县| 礼泉县| 抚州市| 伊金霍洛旗| 景东|