React源碼解析之completeWork和HostText的更新

前言:
React源碼解析之completeUnitOfWork 中,提到了completeWork()的做用是更新該節點(commit階段會將其轉成真實的DOM節點)javascript

本文來解析下completeWork()源碼html

1、completeWork
做用:
根據組件類型的不一樣,進行不一樣的更新操做java

源碼:node

//更新不一樣的組件/節點
function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null 
{
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    //組件的初始狀態
    case IndeterminateComponent:
      break;
    //懶(動態)加載組件
    //https://zh-hans.reactjs.org/docs/react-api.html#reactlazy

    //也能夠看下這篇文章:React的動態加載(lazy import)https://www.jianshu.com/p/27cc69eb4556
    case LazyComponent:
      break;
    //和 React.memo 相似
    //https://zh-hans.reactjs.org/docs/react-api.html#reactmemo
    case SimpleMemoComponent:
    //函數組件
    //https://zh-hans.reactjs.org/docs/components-and-props.html#function-and-class-components
    case FunctionComponent:
      break;
    //類/class 組件
    //https://zh-hans.reactjs.org/docs/components-and-props.html#function-and-class-components
    case ClassComponent: {
      const Component = workInProgress.type;
      //======context 相關,暫時跳過==========================
      if (isLegacyContextProvider(Component)) {
        popLegacyContext(workInProgress);
      }
      break;
    }
    //fiberRoot 節點的更新
    case HostRoot: {
      //出棧操做
      //將 valueStack 棧中指定位置的 value 賦值給不一樣 StackCursor.current
      popHostContainer(workInProgress);
      //同上
      popTopLevelLegacyContextObject(workInProgress);
      // context 相關,可跳過
      const fiberRoot = (workInProgress.stateNode: FiberRoot);
      if (fiberRoot.pendingContext) {
        fiberRoot.context = fiberRoot.pendingContext;
        fiberRoot.pendingContext = null;
      }
      if (current === null || current.child === null) {
        // If we hydrated, pop so that we can delete any remaining children
        // that weren't hydrated.
        popHydrationState(workInProgress);
        // This resets the hacky state to fix isMounted before committing.
        // TODO: Delete this when we delete isMounted and findDOMNode.
        workInProgress.effectTag &= ~Placement;
      }
      updateHostContainer(workInProgress);
      break;
    }
    //DOM 節點的更新,涉及到 virtual dom
    //https://zh-hans.reactjs.org/docs/faq-internals.html#___gatsby
    case HostComponent: {
      popHostContext(workInProgress);

      const rootContainerInstance = getRootHostContainer();
      const type = workInProgress.type;
      //若是不是第一次渲染的話
      if (current !== null && workInProgress.stateNode != null) {
        //更新 DOM 時進行 diff 判斷
        updateHostComponent(
          current,
          workInProgress,
          type,
          newProps,
          rootContainerInstance,
        );
        //ref指向有變更的話,更新 ref
        if (current.ref !== workInProgress.ref) {
          ////添加 Ref 的 EffectTag
          markRef(workInProgress);
        }
      }

      else {
        //若是是第一次渲染的話

        //多是意外終止了
        if (!newProps) {
          invariant(
            workInProgress.stateNode !== null,
            'We must have new props for new mounts. This error is likely ' +
              'caused by a bug in React. Please file an issue.',
          );
          // This can happen when we abort work.
          break;
        }

        const currentHostContext = getHostContext();
        // TODO: Move createInstance to beginWork and keep it on a context
        // "stack" as the parent. Then append children as we go in beginWork
        // or completeWork depending on we want to add then top->down or
        // bottom->up. Top->down is faster in IE11.
        //曾是服務端渲染
        let wasHydrated = popHydrationState(workInProgress);
        //若是是服務端渲染的話,暫時跳過
        if (wasHydrated) {
          // TODO: Move this and createInstance step into the beginPhase
          // to consolidate.
          if (
            prepareToHydrateHostInstance(
              workInProgress,
              rootContainerInstance,
              currentHostContext,
            )
          ) {
            // If changes to the hydrated node needs to be applied at the
            // commit-phase we mark this as such.
            markUpdate(workInProgress);
          }
        }
        //不是服務端渲染
        else {
          //建立 fiber 實例,即 DOM 實例
          let instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );

          appendAllChildren(instance, workInProgress, falsefalse);

          // Certain renderers require commit-time effects for initial mount.
          // (eg DOM renderer supports auto-focus for certain elements).
          // Make sure such renderers get scheduled for later work.
          if (
            //初始化事件監聽
            //若是該節點可以自動聚焦的話
            finalizeInitialChildren(
              instance,
              type,
              newProps,
              rootContainerInstance,
              currentHostContext,
            )
          ) {
            //添加 EffectTag,方便在 commit 階段 update
            markUpdate(workInProgress);
          }
          //將處理好的節點實例綁定到 stateNode 上
          workInProgress.stateNode = instance;
        }
        //若是 ref 引用不爲空的話
        if (workInProgress.ref !== null) {
          // If there is a ref on a host node we need to schedule a callback
          //添加 Ref 的 EffectTag
          markRef(workInProgress);
        }
      }
      break;
    }
    //文本節點的更新
    case HostText: {
      let newText = newProps;
      //若是不是第一次渲染的話
      if (current && workInProgress.stateNode != null) {
        const oldText = current.memoizedProps;
        // If we have an alternate, that means this is an update and we need
        // to schedule a side-effect to do the updates.
        //若是與workInProgress相對於的alternate存在的話,說明有更新
        //那麼就添加 Update 的 effectTag
        updateHostText(current, workInProgress, oldText, newText);
      }
      //若是是第一次渲染的話
      else {
        //當文本節點更新的內容不是 string 類型的話,說明 React 內部出現了 error
        if (typeof newText !== 'string') {
          invariant(
            workInProgress.stateNode !== null,
            'We must have new props for new mounts. This error is likely ' +
              'caused by a bug in React. Please file an issue.',
          );
          // This can happen when we abort work.
        }
        // context 相關,暫時跳過
        const rootContainerInstance = getRootHostContainer();
        const currentHostContext = getHostContext();
        //曾是服務端渲染
        let wasHydrated = popHydrationState(workInProgress);
        //若是是服務端渲染的話,暫時跳過
        if (wasHydrated) {
          if (prepareToHydrateHostTextInstance(workInProgress)) {
            markUpdate(workInProgress);
          }
        }
        //不是服務端渲染
        else {
          //第一次渲染的話,建立文本節點的實例並賦值給 stateNode
          workInProgress.stateNode = createTextInstance(
            newText,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );
        }
      }
      break;
    }
    //React.forwardRef 組件的更新
    //https://zh-hans.reactjs.org/docs/react-api.html#reactforwardref
    case ForwardRef:
      break;
    //suspense 組件的更新
    //https://zh-hans.reactjs.org/docs/concurrent-mode-reference.html#suspense
    case SuspenseComponent: {
      popSuspenseContext(workInProgress);
      const nextState: null | SuspenseState = workInProgress.memoizedState;
      if ((workInProgress.effectTag & DidCapture) !== NoEffect) {
        // Something suspended. Re-render with the fallback children.
        workInProgress.expirationTime = renderExpirationTime;
        // Do not reset the effect list.
        return workInProgress;
      }

      const nextDidTimeout = nextState !== null;
      let prevDidTimeout = false;
      if (current === null) {
        // In cases where we didn't find a suitable hydration boundary we never
        // downgraded this to a DehydratedSuspenseComponent, but we still need to
        // pop the hydration state since we might be inside the insertion tree.
        popHydrationState(workInProgress);
      } else {
        const prevState: null | SuspenseState = current.memoizedState;
        prevDidTimeout = prevState !== null;
        if (!nextDidTimeout && prevState !== null) {
          // We just switched from the fallback to the normal children.
          // Delete the fallback.
          // TODO: Would it be better to store the fallback fragment on
          // the stateNode during the begin phase?
          const currentFallbackChild: Fiber | null = (current.child: any)
            .sibling;
          if (currentFallbackChild !== null) {
            // Deletions go at the beginning of the return fiber's effect list
            const first = workInProgress.firstEffect;
            if (first !== null) {
              workInProgress.firstEffect = currentFallbackChild;
              currentFallbackChild.nextEffect = first;
            } else {
              workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChild;
              currentFallbackChild.nextEffect = null;
            }
            currentFallbackChild.effectTag = Deletion;
          }
        }
      }

      if (nextDidTimeout && !prevDidTimeout) {
        // If this subtreee is running in batched mode we can suspend,
        // otherwise we won't suspend.
        // TODO: This will still suspend a synchronous tree if anything
        // in the concurrent tree already suspended during this render.
        // This is a known bug.
        if ((workInProgress.mode & BatchedMode) !== NoMode) {
          // TODO: Move this back to throwException because this is too late
          // if this is a large tree which is common for initial loads. We
          // don't know if we should restart a render or not until we get
          // this marker, and this is too late.
          // If this render already had a ping or lower pri updates,
          // and this is the first time we know we're going to suspend we
          // should be able to immediately restart from within throwException.
          const hasInvisibleChildContext =
            current === null &&
            workInProgress.memoizedProps.unstable_avoidThisFallback !== true;
          if (
            hasInvisibleChildContext ||
            hasSuspenseContext(
              suspenseStackCursor.current,
              (InvisibleParentSuspenseContext: SuspenseContext),
            )
          ) {
            // If this was in an invisible tree or a new render, then showing
            // this boundary is ok.
            renderDidSuspend();
          } else {
            // Otherwise, we're going to have to hide content so we should
            // suspend for longer if possible.
            renderDidSuspendDelayIfPossible();
          }
        }
      }

      if (supportsPersistence) {
        // TODO: Only schedule updates if not prevDidTimeout.
        if (nextDidTimeout) {
          // If this boundary just timed out, schedule an effect to attach a
          // retry listener to the proimse. This flag is also used to hide the
          // primary children.
          workInProgress.effectTag |= Update;
        }
      }
      if (supportsMutation) {
        // TODO: Only schedule updates if these values are non equal, i.e. it changed.
        if (nextDidTimeout || prevDidTimeout) {
          // If this boundary just timed out, schedule an effect to attach a
          // retry listener to the proimse. This flag is also used to hide the
          // primary children. In mutation mode, we also need the flag to
          // *unhide* children that were previously hidden, so check if the
          // is currently timed out, too.
          workInProgress.effectTag |= Update;
        }
      }
      break;
    }
    //React.Fragment 的更新
    //https://zh-hans.reactjs.org/docs/react-api.html#reactfragment
    case Fragment:
      break;
    //暫時不知道是什麼組件/節點
    case Mode:
      break;
    //Profiler 組件的更新
    //https://zh-hans.reactjs.org/docs/profiler.html#___gatsby
    case Profiler:
      break;
    //React.createportal 節點的更新
    //https://zh-hans.reactjs.org/docs/react-dom.html#createportal
    case HostPortal:
      popHostContainer(workInProgress);
      updateHostContainer(workInProgress);
      break;
    //Context.Provider 組件的更新
    //https://zh-hans.reactjs.org/docs/context.html#contextprovider
    case ContextProvider:
      // Pop provider fiber
      popProvider(workInProgress);
      break;
    //Context.Consumer 組件的更新
    //https://zh-hans.reactjs.org/docs/context.html#contextconsumer
    case ContextConsumer:
      break;
    //React.Memo 組件的更新
    //https://zh-hans.reactjs.org/docs/react-api.html#reactmemo
    case MemoComponent:
      break;
    //未完成/被中斷的 class 組件的更新
    case IncompleteClassComponent: {
      // Same as class component case. I put it down here so that the tags are
      // sequential to ensure this switch is compiled to a jump table.
      const Component = workInProgress.type;
      if (isLegacyContextProvider(Component)) {
        popLegacyContext(workInProgress);
      }
      break;
    }
    //不是 server 端渲染的 suspense 組件的更新
    case DehydratedSuspenseComponent: {
      if (enableSuspenseServerRenderer) {
        popSuspenseContext(workInProgress);
        if (current === null) {
          let wasHydrated = popHydrationState(workInProgress);
          invariant(
            wasHydrated,
            'A dehydrated suspense component was completed without a hydrated node. ' +
              'This is probably a bug in React.',
          );
          if (enableSchedulerTracing) {
            markSpawnedWork(Never);
          }
          skipPastDehydratedSuspenseInstance(workInProgress);
        } else if ((workInProgress.effectTag & DidCapture) === NoEffect) {
          // This boundary did not suspend so it's now hydrated.
          // To handle any future suspense cases, we're going to now upgrade it
          // to a Suspense component. We detach it from the existing current fiber.
          current.alternate = null;
          workInProgress.alternate = null;
          workInProgress.tag = SuspenseComponent;
          workInProgress.memoizedState = null;
          workInProgress.stateNode = null;
        }
      }
      break;
    }
    //SuspenseList 組件的更新
    //https://zh-hans.reactjs.org/docs/concurrent-mode-reference.html#suspenselist
    case SuspenseListComponent: {
      popSuspenseContext(workInProgress);

      const renderState: null | SuspenseListRenderState =
        workInProgress.memoizedState;

      if (renderState === null) {
        // We're running in the default, "independent" mode. We don't do anything
        // in this mode.
        break;
      }

      let didSuspendAlready =
        (workInProgress.effectTag & DidCapture) !== NoEffect;

      let renderedTail = renderState.rendering;
      if (renderedTail === null) {
        // We just rendered the head.
        if (!didSuspendAlready) {
          // This is the first pass. We need to figure out if anything is still
          // suspended in the rendered set.
          const renderedChildren = workInProgress.child;
          // If new content unsuspended, but there's still some content that
          // didn't. Then we need to do a second pass that forces everything
          // to keep showing their fallbacks.

          // We might be suspended if something in this render pass suspended, or
          // something in the previous committed pass suspended. Otherwise,
          // there's no chance so we can skip the expensive call to
          // hasSuspendedChildrenAndNewContent.
          let cannotBeSuspended =
            renderHasNotSuspendedYet() &&
            (current === null || (current.effectTag & DidCapture) === NoEffect);
          let needsRerender =
            !cannotBeSuspended &&
            hasSuspendedChildrenAndNewContent(workInProgress, renderedChildren);
          if (needsRerender) {
            // Rerender the whole list, but this time, we'll force fallbacks
            // to stay in place.
            // Reset the effect list before doing the second pass since that's now invalid.
            workInProgress.firstEffect = workInProgress.lastEffect = null;
            // Reset the child fibers to their original state.
            resetChildFibers(workInProgress, renderExpirationTime);

            // Set up the Suspense Context to force suspense and immediately
            // rerender the children.
            pushSuspenseContext(
              workInProgress,
              setShallowSuspenseContext(
                suspenseStackCursor.current,
                ForceSuspenseFallback,
              ),
            );
            return workInProgress.child;
          }
          // hasSuspendedChildrenAndNewContent could've set didSuspendAlready
          didSuspendAlready =
            (workInProgress.effectTag & DidCapture) !== NoEffect;
        }
        if (didSuspendAlready) {
          cutOffTailIfNeeded(renderState, false);
        }
        // Next we're going to render the tail.
      } else {
        // Append the rendered row to the child list.
        if (!didSuspendAlready) {
          if (isShowingAnyFallbacks(renderedTail)) {
            workInProgress.effectTag |= DidCapture;
            didSuspendAlready = true;
            cutOffTailIfNeeded(renderState, true);
          } else if (
            now() > renderState.tailExpiration &&
            renderExpirationTime > Never
          ) {
            // We have now passed our CPU deadline and we'll just give up further
            // attempts to render the main content and only render fallbacks.
            // The assumption is that this is usually faster.
            workInProgress.effectTag |= DidCapture;
            didSuspendAlready = true;

            cutOffTailIfNeeded(renderState, false);

            // Since nothing actually suspended, there will nothing to ping this
            // to get it started back up to attempt the next item. If we can show
            // them, then they really have the same priority as this render.
            // So we'll pick it back up the very next render pass once we've had
            // an opportunity to yield for paint.

            const nextPriority = renderExpirationTime - 1;
            workInProgress.expirationTime = workInProgress.childExpirationTime = nextPriority;
            if (enableSchedulerTracing) {
              markSpawnedWork(nextPriority);
            }
          }
        }
        if (renderState.isBackwards) {
          // The effect list of the backwards tail will have been added
          // to the end. This breaks the guarantee that life-cycles fire in
          // sibling order but that isn't a strong guarantee promised by React.
          // Especially since these might also just pop in during future commits.
          // Append to the beginning of the list.
          renderedTail.sibling = workInProgress.child;
          workInProgress.child = renderedTail;
        } else {
          let previousSibling = renderState.last;
          if (previousSibling !== null) {
            previousSibling.sibling = renderedTail;
          } else {
            workInProgress.child = renderedTail;
          }
          renderState.last = renderedTail;
        }
      }

      if (renderState.tail !== null) {
        // We still have tail rows to render.
        if (renderState.tailExpiration === 0) {
          // Heuristic for how long we're willing to spend rendering rows
          // until we just give up and show what we have so far.
          const TAIL_EXPIRATION_TIMEOUT_MS = 500;
          renderState.tailExpiration = now() + TAIL_EXPIRATION_TIMEOUT_MS;
        }
        // Pop a row.
        let next = renderState.tail;
        renderState.rendering = next;
        renderState.tail = next.sibling;
        next.sibling = null;

        // Restore the context.
        // TODO: We can probably just avoid popping it instead and only
        // setting it the first time we go from not suspended to suspended.
        let suspenseContext = suspenseStackCursor.current;
        if (didSuspendAlready) {
          suspenseContext = setShallowSuspenseContext(
            suspenseContext,
            ForceSuspenseFallback,
          );
        } else {
          suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
        }
        pushSuspenseContext(workInProgress, suspenseContext);
        // Do a pass over the next row.
        return next;
      }
      break;
    }
    //事件組件 的更新,暫未找到相關資料
    case EventComponent: {
      if (enableFlareAPI) {
        popHostContext(workInProgress);
        const rootContainerInstance = getRootHostContainer();
        const responder = workInProgress.type.responder;
        let eventComponentInstance: ReactEventComponentInstance<
          any,
          any,
          any,
        > | null =
          workInProgress.stateNode;

        if (eventComponentInstance === null) {
          let responderState = null;
          if (__DEV__ && !responder.allowMultipleHostChildren) {
            const hostChildrenCount = getEventComponentHostChildrenCount(
              workInProgress,
            );
            warning(
              (hostChildrenCount || 0) < 2,
              'A "<%s>" event component cannot contain multiple host children.',
              getComponentName(workInProgress.type),
            );
          }
          const getInitialState = responder.getInitialState;
          if (getInitialState !== undefined) {
            responderState = getInitialState(newProps);
          }
          eventComponentInstance = workInProgress.stateNode = createEventComponentInstance(
            workInProgress,
            newProps,
            responder,
            rootContainerInstance,
            responderState || {},
            false,
          );
          markUpdate(workInProgress);
        } else {
          // Update the props on the event component state node
          eventComponentInstance.props = newProps;
          // Update the current fiber
          eventComponentInstance.currentFiber = workInProgress;
          updateEventComponent(eventComponentInstance);
        }
      }
      break;
    }
    default:
      invariant(
        false,
        'Unknown unit of work tag. This error is likely caused by a bug in ' +
          'React. Please file an issue.',
      );
  }

  return null;
}
複製代碼

解析:
乍一看很長,可是 是根據fiber對象的tag屬性區分不一樣的組件/節點,而後不一樣的case內,有不一樣的操做react

應該是羅列了 React 中全部類型的組件和節點,絕大部分能在開發層面中用到git

① 在開發層面用到的組件/節點,均註釋了官網連接,可前去查看做用及使用github

② 主要講HostComponent(下篇文章講)和HostText的更新,由於這兩個是涉及到DOM/文本標籤的更新,典型且經常使用web

2、HostText
做用:
建立或更新文本節點api

源碼:數組

//文本節點的更新
    case HostText: {
      //因爲是文本節點,因此 newProps 是 string 字符串
      let newText = newProps;
      //若是不是第一次渲染的話
      if (current && workInProgress.stateNode != null) {
        const oldText = current.memoizedProps;
        // If we have an alternate, that means this is an update and we need
        // to schedule a side-effect to do the updates.
        //若是與workInProgress相對於的alternate存在的話,說明有更新
        //那麼就添加 Update 的 effectTag
        updateHostText(current, workInProgress, oldText, newText);
      }
      //若是是第一次渲染的話
      else {
        //當文本節點更新的內容不是 string 類型的話,說明 React 內部出現了 error
        if (typeof newText !== 'string') {
          invariant(
            workInProgress.stateNode !== null,
            'We must have new props for new mounts. This error is likely ' +
              'caused by a bug in React. Please file an issue.',
          );
          // This can happen when we abort work.
        }
        // context 相關,暫時跳過
        const rootContainerInstance = getRootHostContainer();
        const currentHostContext = getHostContext();
        //曾是服務端渲染
        let wasHydrated = popHydrationState(workInProgress);
        //若是是服務端渲染的話,暫時跳過
        if (wasHydrated) {
          if (prepareToHydrateHostTextInstance(workInProgress)) {
            markUpdate(workInProgress);
          }
        }
        //不是服務端渲染
        else {
          //第一次渲染的話,建立文本節點的實例並賦值給 stateNode
          workInProgress.stateNode = createTextInstance(
            newText,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );
        }
      }
      break;
    }
複製代碼

解析:
(1) 若是不是第一次渲染的話,則執行updateHostText()來更新文本節點

(2) 若是是第一次渲染的話,則執行createTextInstance(),來建立文本節點的實例並賦值給 stateNode

3、updateHostText
做用:
判斷更新文本節點

源碼:

//判斷文本節點是否須要更新
  updateHostText = function(
    current: Fiber,
    workInProgress: Fiber,
    oldText: string,
    newText: string,
  
{
    // If the text differs, mark it as an update. All the work in done in commitWork.
    //因爲文本就是 string,可直接經過 === 判斷便可
    if (oldText !== newText) {
      //添加 Update 的 EffectTag
      markUpdate(workInProgress);
    }
  };
複製代碼

解析:
文本節點判斷是否更新,直接使用===便可

4、markUpdate
做用:
添加UpdateEffectTag

源碼:

//添加 Update 的 EffectTag
function markUpdate(workInProgress: Fiber{
  // Tag the fiber with an update effect. This turns a Placement into
  // a PlacementAndUpdate.
  workInProgress.effectTag |= Update;
}
複製代碼

解析:
添加反作用後,會在 commit 階段進行真正的更新

5、createTextInstance
做用:
建立文本節點的實例

源碼:

//建立文本節點的實例
export function createTextInstance(
  text: string,
  rootContainerInstance: Container,
  hostContext: HostContext,
  internalInstanceHandle: Object,
): TextInstance 
{
  //刪除了 dev 代碼

  //建立文本節點
  const textNode: TextInstance = createTextNode(text, rootContainerInstance);
  //將 fiber 對象做爲文本節點的屬性 __reactInternalInstance,
  //方便從節點上找到 fiber 對象
  precacheFiberNode(internalInstanceHandle, textNode);
  return textNode;
}
複製代碼

解析:
(1) 執行createTextNode()來建立文本節點

(2) 執行precacheFiberNode(),將fiber對象做爲文本節點的屬性

我寫了一個簡單的例子來驗證:

function App({
  const [text, setText] = React.useState(null);

  useEffect(()=>{

    setTimeout(()=>{
      setText('aaa')
    },5000)

  },[])

  return (
    <>
      {text}
      </>
  );
}

export default App;
複製代碼

5秒後建立文本節點:

經過斷點能夠看到:經過textNode__reactInternalInstance$mru28dy3wf屬性,可找到其fiber對象

6、createTextNode
做用:
建立文本節點

源碼:

//建立文本節點
export function createTextNode(
  text: string,
  rootContainerElement: Element | Document,
): Text 
{
  //獲取 document 對象後,經過 document.createTextNode 來建立文本節點
  //詳情請看:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createTextNode
  return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(
    text,
  );
}
複製代碼

解析:
(1) 執行getOwnerDocumentFromRootContainer獲取document對象:

//獲取根節點的 document 對象
function getOwnerDocumentFromRootContainer(
  rootContainerElement: Element | Document,
): Document 
{

  return rootContainerElement.nodeType === DOCUMENT_NODE
    ? (rootContainerElement: any)
    : rootContainerElement.ownerDocument;
}
複製代碼

(2) 調用document.createTextNode()來建立文本節點,詳情請看:
developer.mozilla.org/zh-CN/docs/…

注意:
這裏還處於reconciliation(diff階段),因此textNode是一個對象,
到了commit(操做DOM階段)後,才轉爲DOM中的文本節點

7、precacheFiberNode
做用:
fiber對象做爲textNode的屬性

源碼:

const randomKey = Math.random()
  //轉成 36 進制
  .toString(36)
  //從index=2開始截取
  .slice(2);
//隨機 key
const internalInstanceKey = '__reactInternalInstance$' + randomKey;

//將 fiber 對象賦值到 DOM 節點上
export function precacheFiberNode(hostInst, node{
  node[internalInstanceKey] = hostInst;
}
複製代碼

解析:

8、GitHub
ReactFiberCompleteWork.js
github.com/AttackXiaoJ…

ReactDOMHostConfig.js
github.com/AttackXiaoJ…

ReactDOMComponent.js
github.com/AttackXiaoJ…


(完)

相關文章
相關標籤/搜索