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

1,693 阅读34分钟

前言:
React源码解析之completeUnitOfWork 中,提到了completeWork()的作用是更新该节点(commit阶段会将其转成真实的DOM节点)

本文来解析下completeWork()源码

一、completeWork
作用:
根据组件类型的不同,进行不同的更新操作

源码:

//更新不同的组件/节点
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 中所有类型的组件和节点,绝大部分能在开发层面中用到

① 在开发层面用到的组件/节点,均注释了官网链接,可前去查看作用及使用

② 主要讲HostComponent(下篇文章讲)和HostText的更新,因为这两个是涉及到DOM/文本标签的更新,典型且常用

二、HostText
作用:
创建或更新文本节点

源码:

//文本节点的更新
    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

三、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);
    }
  };

解析:
文本节点判断是否更新,直接使用===即可

四、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 阶段进行真正的更新

五、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对象

六、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中的文本节点

七、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;
}

解析:

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

ReactDOMHostConfig.js
github.com/AttackXiaoJ…

ReactDOMComponent.js
github.com/AttackXiaoJ…


(完)