React源码解析之updateHostComponent和updateHostText

1,340 阅读5分钟

前言:
还是在 React源码解析之workLoop 中,有一段HostComponentHostText的更新:

  case HostComponent:
      //更新 DOM 标签
      return updateHostComponent(current, workInProgress, renderExpirationTime);
  case HostText:
      //更新文本节点
      return updateHostText(current, workInProgress);

本文就解析下updateHostComponent()updateHostText()方法

一、updateHostComponent
作用:
更新DOM标签

源码:

//更新 DOM 标签
function updateHostComponent(current, workInProgress, renderExpirationTime{
  //===暂时跳过 context
  pushHostContext(workInProgress);
  //判断能否复用服务端渲染的节点
  if (current === null) {
    tryToClaimNextHydratableInstance(workInProgress);
  }

  const type = workInProgress.type;
  const nextProps = workInProgress.pendingProps;
  const prevProps = current !== null ? current.memoizedProps : null;

  let nextChildren = nextProps.children;
  //判断该节点是否是文本节点
  const isDirectTextChild = shouldSetTextContent(type, nextProps);
  //如果是文本节点的话(即里面不再嵌套其他类型的节点)
  if (isDirectTextChild) {
    // We special case a direct text child of a host node. This is a common
    // case. We won't handle it as a reified child. We will instead handle
    // this in the host environment that also have access to this prop. That
    // avoids allocating another HostText fiber and traversing it.
    //不必渲染子节点,直接显示其文本即可
    nextChildren = null;
  }
  //如果之前节点不为空且为文本节点,但现在更新为其他类型的节点的话
  else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
    // If we're switching from a direct text child to a normal child, or to
    // empty, we need to schedule the text content to be reset.
    //重置文本节点
    workInProgress.effectTag |= ContentReset;
  }
  //只有 HostComponent 和 ClassComponent 有使用该方法
  //因为只有这两个 Component 能拿到 DOM 实例
  markRef(current, workInProgress);

  // Check the host config to see if the children are offscreen/hidden.
  //如果该节点上设置了 hidden 属性,并且是异步渲染(ConcurrentMode)的话,那么它将最后更新

  //关于 ConcurrentMode 模式,请参考:https://zh-hans.reactjs.org/docs/concurrent-mode-intro.html
  if (
    workInProgress.mode & ConcurrentMode &&
    renderExpirationTime !== Never &&
    shouldDeprioritizeSubtree(type, nextProps)
  ) {
    if (enableSchedulerTracing) {
      markSpawnedWork(Never);
    }
    // Schedule this fiber to re-render at offscreen priority. Then bailout.
    //优先级最低,即最后更新
    workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
    return null;
  }
  //将 ReactElement 变成 fiber对象,并更新,生成对应 DOM 的实例,并挂载到真正的 DOM 节点上
  reconcileChildren(
    current,
    workInProgress,
    nextChildren,
    renderExpirationTime,
  );
  return workInProgress.child;
}

解析:
(1) context相关的以后讲
(2) tryToClaimNextHydratableInstance()方法的作用是判断能否复用服务端渲染的root内部已有的DOM节点
(3) shouldSetTextContent()的作用是判断该节点是否是文本节点:

//判断是否是文本节点
export function shouldSetTextContent(type: string, props: Props): boolean {
  return (
    type === 'textarea' ||
    type === 'option' ||
    type === 'noscript' ||
    typeof props.children === 'string' ||
    typeof props.children === 'number' ||
    (typeof props.dangerouslySetInnerHTML === 'object' &&
      props.dangerouslySetInnerHTML !== null &&
      props.dangerouslySetInnerHTML.__html != null)
  );
}

type应该表示html里的标签,如<textarea><option>noscript
props.children指节点里的内容是否是字符串还是数字
dangerouslySetInnerHTMLinnerHTML,里面内容也是字符串

关于dangerouslySetInnerHTML的介绍与使用,请参考:zh-hans.reactjs.org/docs/dom-el…

也就是说,一旦shouldSetTextContent()判断为true,就确定该节点为文本节点

(4) 如果isDirectTextChildtrue,则表示其内部是文本,故直接渲染即可,nextChildren置为null,后面讲到的updateHostText()的源码也是类似的

(5) 如果之前节点不为空且为文本节点,但现在更新为其他类型的节点的话,则设一个ContentReset的标签

(6) markRef的作用是标记ref
只有HostComponentClassComponent有使用该方法,因为只有这两个Component能直接获取到DOM实例的引用:

//标记 ref
function markRef(current: Fiber | null, workInProgress: Fiber{
  const ref = workInProgress.ref;
  if (
    (current === null && ref !== null) ||
    (current !== null && current.ref !== ref)
  ) {
    // Schedule a Ref effect
    workInProgress.effectTag |= Ref;
  }
}

如果是第一次渲染并且设置了 ref 引用的话,或者不是第一次渲染,但是 ref 的引用发生变化的话,则设置Ref标签

(7) 如果设置了ConcurrentMode模式,并且渲染的优先级不是最低的Never的话,则将该节点的更新优先级重置为最低优先级Neverreturn null则表示不更新

ConcurrentMode模式,我的理解是异步渲染 UI(随时暂停,随时切换),应该是 React 17 会发布到稳定版的新特性,对此模式感兴趣的同学,请参考:zh-hans.reactjs.org/docs/concur…

(8) 如果 (7) 条件不成立的话,则往下执行reconcileChildren(),将 ReactElement 变成 fiber对象,并更新,生成对应 DOM 的实例,并挂载到真正的 DOM 节点上
关于reconcileChildren()的讲解,请参考:React源码解析之FunctionComponent(上)

二、updateHostText
作用:
更新 host 文本节点

源码:

//更新 host 文本节点
function updateHostText(current, workInProgress{
  if (current === null) {
    tryToClaimNextHydratableInstance(workInProgress);
  }
  // Nothing to do here. This is terminal. We'll do the completion step
  // immediately after.
  //没有对 DOM 进行操作的地方,直接渲染出来即可
  return null;
}

解析:
一、updateHostComponent中的(4)相似,文本节点直接渲染出来即可。


(完)