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

通過(guò)示例源碼解讀React首次渲染流程

 更新時(shí)間:2023年03月27日 12:04:52   作者:Aaaaaaaaaaayou  
這篇文章主要為大家通過(guò)示例源碼解讀React的首次渲染流程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

說(shuō)明

本文結(jié)論均基于 React 16.13.1 得出,若有出入請(qǐng)參考對(duì)應(yīng)版本源碼。參考了 React 技術(shù)揭秘。

題目

在開(kāi)始進(jìn)行源碼分析前,我們先來(lái)看幾個(gè)題目:

題目一:

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

import React from 'react'
const channel = new MessageChannel()
// onmessage 是一個(gè)宏任務(wù)
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

題目二:

點(diǎn)擊 p 標(biāo)簽后,下面事件發(fā)生的順序

  • 頁(yè)面顯示 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>
}
// 點(diǎn)擊后,下面事件發(fā)生的順序
// 1. 頁(yè)面顯示 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

你是不是都答對(duì)了呢?

首次渲染流程

我們以下面這個(gè)例子來(lái)闡述下首次渲染的流程:

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 中導(dǎo)出的,并最后調(diào)用了 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)
}

首次渲染時(shí),經(jīng)過(guò)下面這一系列的操作,會(huì)初始化一些東西:

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
}

經(jīng)過(guò)這一系列的操作以后,會(huì)形成如下的數(shù)據(jù)結(jié)構(gòu):

然后,會(huì)來(lái)到:

unbatchedUpdates(() => {
  // 這里的 children 是 App 對(duì)應(yīng)的這個(gè) ReactElement
  updateContainer(children, fiberRoot, parentComponent, callback)
})

這里 unbatchedUpdates 會(huì)設(shè)置當(dāng)前的 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()
    }
  }
}

然后執(zhí)行 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
}

這里,會(huì)創(chuàng)建一個(gè) update,然后入隊(duì),我們的數(shù)據(jù)結(jié)構(gòu)會(huì)變成這樣:

接下來(lái)就到了 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 {
      // 暫時(shí)不看
    }
  } else {
    // 暫時(shí)不看
  }
}

最后走到了 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
}

這里,可以分為兩個(gè)大的步驟:

  • 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
}

這里首先調(diào)用 prepareFreshStack(root, expirationTime),這一句主要是通過(guò) root.current 來(lái)創(chuàng)建 workInProgress。調(diào)用后,數(shù)據(jù)結(jié)構(gòu)成了這樣:

跳過(guò)中間的一些語(yǔ)句,我們來(lái)到 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
}

這里又分為兩個(gè)步驟:

  • beginWork,傳入當(dāng)前 Fiber 節(jié)點(diǎn),創(chuàng)建子 Fiber 節(jié)點(diǎn)。
  • completeUnitOfWork,通過(guò) Fiber 節(jié)點(diǎn)創(chuàng)建真實(shí) DOM 節(jié)點(diǎn)。

這兩個(gè)步驟會(huì)交替的執(zhí)行,其目標(biāo)是:

  • 構(gòu)建出新的 Fiber 樹(shù)
  • 與舊 Fiber 比較得到 effect 鏈表(插入、更新、刪除、useEffect 等都會(huì)產(chǎn)生 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:
    // ...省略
    // ...省略其他類型
  }
}

這里因?yàn)槭?rootFiber,所以會(huì)走到 updateHostRoot

function updateHostRoot(current, workInProgress, renderExpirationTime) {
  // 暫時(shí)不看
  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
}

經(jīng)過(guò) updateHostRoot 后,會(huì)返回 workInProgress.child 作為下一個(gè) workInProgress,最后的數(shù)據(jù)結(jié)構(gòu)如下(這里先忽略 reconcileChildren 這個(gè)比較復(fù)雜的函數(shù)):

接著會(huì)繼續(xù)進(jìn)行 beginWork,這次會(huì)來(lái)到 mountIndeterminateComponent (暫時(shí)忽略)??傊?jīng)過(guò)不斷的 beginWork 后,我們會(huì)得到如下的一個(gè)結(jié)構(gòu):

此時(shí) next 為空,我們會(huì)走到:

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
  }
}

此時(shí)這里的 unitOfWorkspan 對(duì)應(yīng)的 fiber。從函數(shù)頭部的注釋我們可以大致知道該函數(shù)的功能:

// 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.
// 嘗試去完成當(dāng)前的工作單元,然后處理下一個(gè) sibling。如果沒(méi)有 sibling 了,就返回去完成父 fiber

這里一路走下去最后會(huì)來(lái)到 completeWork 這里 :

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

執(zhí)行完后,我們的結(jié)構(gòu)如下所示(我們用綠色的圓來(lái)表示真實(shí) dom):

此時(shí) next 將會(huì)是 null,我們需要往上找到下一個(gè) completedWork,即 Name,因?yàn)?Name 是一個(gè) FunctionComponent,所以在 completeWork 中直接返回了 null。又因?yàn)樗?sibling,所以會(huì)將它的 sibling 賦值給 workInProgress,并返回對(duì)其進(jìn)行 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 不斷交替的執(zhí)行,當(dāng)我們執(zhí)行到 div 的時(shí)候,我們的結(jié)構(gòu)如下所示:

之所以要額外的分析 divcomplete 過(guò)程,是因?yàn)檫@個(gè)例子方便我們分析 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 這個(gè) fiber,他的 childName,會(huì)進(jìn)入 else if (node.child !== null) 這個(gè)條件分支。然后繼續(xù)下一個(gè)循環(huán),此時(shí) nodespan 這個(gè) fiber,會(huì)進(jìn)入第一個(gè)分支,將 span 對(duì)應(yīng)的 dom 元素插入到 parent 之中。

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

然后繼續(xù) beginWorkcompleteWork,最后會(huì)來(lái)到 rootFiber。不同的是,該節(jié)點(diǎn)的 alternate 并不為空,且該節(jié)點(diǎn) tagHootRoot,所以 completeWork 時(shí)會(huì)來(lái)到這里:

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

看來(lái)幾乎沒(méi)有做什么事情,到這我們的 render 階段就結(jié)束了,最后的結(jié)構(gòu)如下所示:

其中藍(lán)色表示是有 effect 的 Fiber 節(jié)點(diǎn),他們組成了一個(gè)鏈表,方便 commit 過(guò)程進(jìn)行遍歷。

可以查看 render 過(guò)程動(dòng)畫(huà)。

commit

commit 大致可分為以下過(guò)程:

  • 準(zhǔn)備階段
  • before mutation 階段(執(zhí)行 DOM 操作前)
  • mutation 階段(執(zhí)行 DOM 操作)
  • 切換 Fiber Tree
  • layout 階段(執(zhí)行 DOM 操作后)
  • 收尾階段

準(zhǔn)備階段

do {
  // 觸發(fā)useEffect回調(diào)與其他同步任務(wù)。由于這些任務(wù)可能觸發(fā)新的渲染,所以這里要一直遍歷執(zhí)行直到?jīng)]有任務(wù)
  flushPassiveEffects()
  // 暫時(shí)沒(méi)有復(fù)現(xiàn)出 rootWithPendingPassiveEffects !== null 的情景
  // 首次渲染 rootWithPendingPassiveEffects 為 null
} while (rootWithPendingPassiveEffects !== null)
// finishedWork 就是正在工作的 rootFiber
const finishedWork = root.
// 優(yōu)先級(jí)相關(guān)暫時(shí)不看
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
// 由于每個(gè)fiber的effectList只包含他的子孫節(jié)點(diǎn)
// 所以根節(jié)點(diǎn)如果有effectTag則不會(huì)被包含進(jìn)來(lái)
// 所以這里將有effectTag的根節(jié)點(diǎn)插入到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
}

準(zhǔn)備階段主要是確定 firstEffect,我們的例子中就是 Name 這個(gè) 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 階段主要是調(diào)用了 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
      // 調(diào)用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
  }
}

因?yàn)?NameeffectTag 包括了 Passive,所以這里會(huì)執(zhí)行:

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

這里主要是對(duì) useEffect 中的任務(wù)進(jìn)行異步調(diào)用,最終會(huì)在下個(gè)事件循環(huán)中執(zhí)行 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 會(huì)執(zhí)行 useEffect 上次渲染返回的 destroy 方法,commitHookEffectListMount 會(huì)執(zhí)行 useEffect 本次渲染的 create 方法。具體到我們的例子:

因?yàn)槭鞘状武秩?,所?destroy 都是 undefined,所以只會(huì)打印 useEffect ayou。

mutation 階段

mutation 階段主要是執(zhí)行了 commitMutationEffects 這個(gè)方法:

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 會(huì)走 Update 這個(gè)分支,執(zhí)行 commitWork,最終會(huì)執(zhí)行到 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);
  }
}

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

App 會(huì)走到 Placement 這個(gè)分支,執(zhí)行 commitPlacement,這里的主要工作是把整棵 dom 樹(shù)插入到了 <div id='root'></div> 之中。

切換 Fiber Tree

mutation 階段完成后,會(huì)執(zhí)行:

root.current = finishedWork

完成后, fiberRoot 會(huì)指向 current Fiber 樹(shù)。

layout 階段

對(duì)應(yīng)到我們的例子,layout 階段主要是同步執(zhí)行 useLayoutEffect 中的 create 函數(shù),所以這里會(huì)打印 useLayoutEffect ayou。

題目解析

現(xiàn)在,我們來(lái)分析下文章開(kāi)始的兩個(gè)題目:

題目一:

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

import React from 'react'
const channel = new MessageChannel()
// onmessage 是一個(gè)宏任務(wù)
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 中的任務(wù)會(huì)跟隨渲染過(guò)程同步執(zhí)行,所以先打印 4
  • Promise 對(duì)象 then 中的任務(wù)是一個(gè)微任務(wù),所以在 4 后面執(zhí)行,打印 3
  • console.log('1 message channel')console.log('2 use effect') 都會(huì)在宏任務(wù)中執(zhí)行,執(zhí)行順序就看誰(shuí)先生成,這里 2 比 1 先,所以先打印 2,再打印 1。

題目二:

點(diǎn)擊 p 標(biāo)簽后,下面事件發(fā)生的順序

  • 頁(yè)面顯示 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>
}
// 點(diǎn)擊后,下面事件發(fā)生的順序
// 1. 頁(yè)面顯示 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 這個(gè) Fiber 位于 effect 鏈表的首部,在 commitMutations 中會(huì)先處理,所以頁(yè)面先顯示 xingzhi。
  • Name 這個(gè) Fiber 位于 span 之后,所以 useLayoutEffect 中上一次的 destroy 緊接著其執(zhí)行。打印 useLayoutEffect ayou。
  • commitLayoutEffects 中執(zhí)行 useLayoutEffect 這一次的 create。打印 useLayoutEffect xingzhi。
  • useEffect 在下一個(gè)宏任務(wù)中執(zhí)行,先執(zhí)行上一次的 destroy,再執(zhí)行這一次的 create。所以先打印 useEffect ayou,再打印 useEffect xingzhi。

總結(jié)

本文大部分內(nèi)容都參考自 React 技術(shù)揭秘,通過(guò)舉例及畫(huà)圖走讀了一遍首次渲染流程,加深了下自己的理解。

以上就是通過(guò)示例源碼解讀React首次渲染流程的詳細(xì)內(nèi)容,更多關(guān)于React首次渲染流程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • react?app?rewrited替代品craco使用示例

    react?app?rewrited替代品craco使用示例

    這篇文章主要為大家介紹了react?app?rewrited替代品craco使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • 在React 組件中使用Echarts的示例代碼

    在React 組件中使用Echarts的示例代碼

    本篇文章主要介紹了在React 組件中使用Echarts的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-11-11
  • react+axios實(shí)現(xiàn)github搜索用戶功能(示例代碼)

    react+axios實(shí)現(xiàn)github搜索用戶功能(示例代碼)

    這篇文章主要介紹了react+axios實(shí)現(xiàn)搜索github用戶功能,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • React Native可復(fù)用 UI分離布局組件和狀態(tài)組件技巧

    React Native可復(fù)用 UI分離布局組件和狀態(tài)組件技巧

    這篇文章主要為大家介紹了React Native可復(fù)用 UI分離布局組件和狀態(tài)組件使用技巧,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • React優(yōu)化子組件render的使用

    React優(yōu)化子組件render的使用

    這篇文章主要介紹了React優(yōu)化子組件render的使用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-05-05
  • React DnD如何處理拖拽詳解

    React DnD如何處理拖拽詳解

    這篇文章主要為大家介紹了React DnD如何處理拖拽示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • React?Fiber構(gòu)建源碼解析

    React?Fiber構(gòu)建源碼解析

    這篇文章主要為大家介紹了React?Fiber構(gòu)建源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • React+高德地圖實(shí)時(shí)獲取經(jīng)緯度,定位地址

    React+高德地圖實(shí)時(shí)獲取經(jīng)緯度,定位地址

    思路其實(shí)沒(méi)有那么復(fù)雜,把地圖想成一個(gè)盒子容器,地圖中心點(diǎn)想成盒子中心點(diǎn);扎點(diǎn)在【地圖中心點(diǎn)】不會(huì)動(dòng),當(dāng)移動(dòng)地圖時(shí),去獲取【地圖中心點(diǎn)】經(jīng)緯度,設(shè)置某個(gè)位置的時(shí)候,將經(jīng)緯度設(shè)置為【地圖中心點(diǎn)】即可
    2021-06-06
  • 詳解使用WebPack搭建React開(kāi)發(fā)環(huán)境

    詳解使用WebPack搭建React開(kāi)發(fā)環(huán)境

    這篇文章主要介紹了詳解使用WebPack搭建React開(kāi)發(fā)環(huán)境,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • 使用react-dnd編寫(xiě)一個(gè)可拖拽排列的list

    使用react-dnd編寫(xiě)一個(gè)可拖拽排列的list

    這篇文章主要為大家詳細(xì)介紹了如何使用react-dnd編寫(xiě)一個(gè)可拖拽排列的list,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-03-03

最新評(píng)論