React16源码: React中的completeWork对HostComponent处理的源码实现

news/2024/7/15 18:08:20 标签: react.js, 前端, 前端框架

HostComponent


1 )概述

  • completeWork 当中,我们需要对 HostComponent 的一些操作有哪些?
    • 首先在一次更新而不是初次渲染的情况下
    • 需要去 diffProperties 来计算,需要更新的内容
    • 也就是在 vdom 中去进行一个对比来判断这一个节点是否需要真的去更新它
    • 以此来最低程度的去更新整个 dom 的一个过程
    • 对于不同 dom property,它有一些不同的处理方式

2 )源码

定位到 packages/react-reconciler/src/ReactFiberCompleteWork.js#L581

找到 case HostComponent

case HostComponent: {
  // 跳过
  popHostContext(workInProgress);
  // 跳过
  const rootContainerInstance = getRootHostContainer();
  const type = workInProgress.type;
  // 不是第一次渲染,也就是更新的时候
  if (current !== null && workInProgress.stateNode != null) {
    updateHostComponent(
      current,
      workInProgress,
      type,
      newProps,
      rootContainerInstance,
    );

    if (current.ref !== workInProgress.ref) {
      markRef(workInProgress); // 跳过
    }
  } else {
    // 第一次挂载渲染
    // 没有 props 说明有问题,提示
    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 {
      // 第一次挂载
      // 注意,这里,创建 element的过程
      let instance = createInstance(
        type,
        newProps,
        rootContainerInstance,
        currentHostContext,
        workInProgress,
      );

      // 第一次渲染,要添加子节点
      appendAllChildren(instance, workInProgress, false, false);

      // 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 (
        // 主要功能: 对dom节点上面的可能的事件监听,需要初始化整个react的事件体系
        finalizeInitialChildren(
          instance,
          type,
          newProps,
          rootContainerInstance,
          currentHostContext,
        )
      ) {
        markUpdate(workInProgress);
      }
      workInProgress.stateNode = instance;
    }

    if (workInProgress.ref !== null) {
      // If there is a ref on a host node we need to schedule a callback
      markRef(workInProgress);
    }
  }
  break;
}
  • 进入 createInstance
    // packages/react-dom/src/client/ReactDOMHostConfig.js#L167
    // 这个方法就是创建节点的过程
    export function createInstance(
      type: string,
      props: Props,
      rootContainerInstance: Container,
      hostContext: HostContext,
      internalInstanceHandle: Object,
    ): Instance {
      let parentNamespace: string;
      if (__DEV__) {
        // TODO: take namespace into account when validating.
        const hostContextDev = ((hostContext: any): HostContextDev);
        validateDOMNesting(type, null, hostContextDev.ancestorInfo);
        if (
          typeof props.children === 'string' ||
          typeof props.children === 'number'
        ) {
          const string = '' + props.children;
          const ownAncestorInfo = updatedAncestorInfo(
            hostContextDev.ancestorInfo,
            type,
          );
          validateDOMNesting(null, string, ownAncestorInfo);
        }
        parentNamespace = hostContextDev.namespace;
      } else {
        parentNamespace = ((hostContext: any): HostContextProd);
      }
      // 主要在这里
      const domElement: Instance = createElement(
        type,
        props,
        rootContainerInstance,
        parentNamespace,
      );
      // 后面会进入这个方法
      precacheFiberNode(internalInstanceHandle, domElement);
      // 同上
      updateFiberProps(domElement, props);
      return domElement;
    }
    
  • 进入 createElement
    // 不展开这个方法里面调用的内容
    export function createElement(
      type: string,
      props: Object,
      rootContainerElement: Element | Document,
      parentNamespace: string,
    ): Element {
      let isCustomComponentTag;
    
      // We create tags in the namespace of their parent container, except HTML
      // tags get no namespace.
      // 先要获取 document, 先要通过 document 的api来创建
      const ownerDocument: Document = getOwnerDocumentFromRootContainer(
        rootContainerElement,
      );
      let domElement: Element;
      let namespaceURI = parentNamespace;
      // 在React中有区分,不同节点类型,都会有一个定义,比如html普通节点, svg节点等
      if (namespaceURI === HTML_NAMESPACE) {
        namespaceURI = getIntrinsicNamespace(type);
      }
      // 如果是 html 类型节点,做一些特殊处理
      if (namespaceURI === HTML_NAMESPACE) {
        if (__DEV__) {
          isCustomComponentTag = isCustomComponent(type, props);
          // Should this check be gated by parent namespace? Not sure we want to
          // allow <SVG> or <mATH>.
          warning(
            isCustomComponentTag || type === type.toLowerCase(),
            '<%s /> is using incorrect casing. ' +
              'Use PascalCase for React components, ' +
              'or lowercase for HTML elements.',
            type,
          );
        }
        // 对script节点进行特殊处理
        if (type === 'script') {
          // Create the script via .innerHTML so its "parser-inserted" flag is
          // set to true and it does not execute
          const div = ownerDocument.createElement('div');
          div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
          // This is guaranteed to yield a script element.
          const firstChild = ((div.firstChild: any): HTMLScriptElement);
          domElement = div.removeChild(firstChild);
        } else if (typeof props.is === 'string') {
          // $FlowIssue `createElement` should be updated for Web Components
          domElement = ownerDocument.createElement(type, {is: props.is});
        } else {
          // Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.
          // See discussion in https://github.com/facebook/react/pull/6896
          // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
          domElement = ownerDocument.createElement(type);
          // Normally attributes are assigned in `setInitialDOMProperties`, however the `multiple`
          // attribute on `select`s needs to be added before `option`s are inserted. This prevents
          // a bug where the `select` does not scroll to the correct option because singular
          // `select` elements automatically pick the first item.
          // See https://github.com/facebook/react/issues/13222
          if (type === 'select' && props.multiple) {
            const node = ((domElement: any): HTMLSelectElement);
            node.multiple = true;
          }
        }
      } else {
        domElement = ownerDocument.createElementNS(namespaceURI, type);
      }
    
      if (__DEV__) {
        if (namespaceURI === HTML_NAMESPACE) {
          if (
            !isCustomComponentTag &&
            Object.prototype.toString.call(domElement) ===
              '[object HTMLUnknownElement]' &&
            !Object.prototype.hasOwnProperty.call(warnedUnknownTags, type)
          ) {
            warnedUnknownTags[type] = true;
            warning(
              false,
              'The tag <%s> is unrecognized in this browser. ' +
                'If you meant to render a React component, start its name with ' +
                'an uppercase letter.',
              type,
            );
          }
        }
      }
    
      return domElement;
    }
    
  • 进入 precacheFiberNode
    const internalInstanceKey = '__reactInternalInstance$' + randomKey;
    
    // 就是在node上挂载上述属性
    // hostInst, node 对应着 internalInstanceHandle, domElement
    // 相当于在 对应dom节点上,通过 internalInstanceKey 这个 key
    // 去创建一个指向 fiber 对象的引用
    // 后期,我们想要从dom对象上获取对应的 fiber就可以方便获取
    export function precacheFiberNode(hostInst, node) {
      node[internalInstanceKey] = hostInst;
    }
    
  • 进入 updateFiberProps
    const internalEventHandlersKey = '__reactEventHandlers$' + randomKey;
    
    // 两个参数 node 和 props, 对应着 domElement, props
    // 在 domElement 上用一个key 存储 props
    // props 会对应到上面 domElement 的 attribute, 所以,有对应关系,方便后期取用
    export function updateFiberProps(node, props) {
      node[internalEventHandlersKey] = props;
    }
    
  • 进入 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;
      // 这个循环的意义:对当前节点下寻找第一层的dom节点或text节点,不会append 嵌套(继续下一层)的dom节点
      // 这样做的原因,因为对每个dom节点都会有这样一个执行 completeWork 的阶段
      // 也就是当前层只找自己下一层的dom或text, 一层一层的向下找,不会存在忽略的问题
      while (node !== null) {
        if (node.tag === HostComponent || node.tag === HostText) {
          // 注意这里,挂载节点 
          // parent 是刚刚创建的 instance
          // node.stateNode 是当前节点子节点对应的Fiber对象
          // 如果说,它的子节点那一层上面发现了原生dom节点或者文本节点,就把它挂载到节点上面
          appendInitialChild(parent, node.stateNode);
        } 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;
        }
        // 这里直接 return
        if (node === workInProgress) {
          return;
        }
        // 没有兄弟节点,向上遍历
        while (node.sibling === null) {
          if (node.return === null || node.return === workInProgress) {
            return;
          }
          node = node.return;
        }
        // 这个本来如此,对sibling进行挂载操作
        node.sibling.return = node.return;
        // 循环找 sibling 节点
        node = node.sibling;
      }
    };
    
  • 进入 appendInitialChild
    // 这个函数执行的就是 dom 节点的 appendChild方法
    export function appendInitialChild(
      parentInstance: Instance,
      child: Instance | TextInstance,
    ): void {
      parentInstance.appendChild(child);
    }
    
  • 以上,相关注释都在代码内,不再单独拿出详解
  • 里面没涉及到的一些代码,都比较简单,不再赘述
  • 主要关注上面 appendAllChildren 的算法, 内部有注释

http://www.niftyadmin.cn/n/5340701.html

相关文章

如何用AirServer进行手机投屏?,Airserver 永久激活注册码

AirServer一款投屏神器&#xff0c;可以帮你轻松地将iPhone、iPad投屏到Mac。是不是经常看到游戏主播用AirServer投屏&#xff1f;此外&#xff0c;AirServer也是视频Up主必备工具之一&#xff01;用来录制演示教程不错。除了实现单个手机投屏到电脑或荧幕。如果你有多画面投屏…

深度学习算法应用实战 | DINOv2 图像相似度实战

特征提取简介 什么是特征提取 特征提取器负责为音频或视觉模型准备输入特征。包括从序列中提取特征&#xff0c;例如&#xff0c;对音频文件进行预处理以生成对数梅尔频谱图特征。以及从图像中提取特征&#xff0c;例如裁剪图像文件&#xff0c;还包括填充、归一化以及转换为N…

大数据学习之Flink,了解Flink的多种部署模式

目录 一、部署模式 1. 部署模式分类&#xff1a; 1.1 会话模式&#xff08;Session Mode&#xff09;&#xff1a; 优点&#xff1a; 缺点&#xff1a; 1.2 单作业模式&#xff08;Per-Job Mode&#xff09;&#xff1a; 优点&#xff1a; ​ 缺点&#xff1a; ​ 1.3…

SqlAlchemy使用教程(六) -- ORM 表间关系的定义与CRUD操作

SqlAlchemy使用教程(一) 原理与环境搭建SqlAlchemy使用教程(二) 入门示例及编程步骤SqlAlchemy使用教程(三) CoreAPI访问与操作数据库详解SqlAlchemy使用教程(四) MetaData 与 SQL Express Language 的使用SqlAlchemy使用教程(五) ORM API 编程入门 本章内容&#xff0c;稍微有…

020-信息打点-红蓝队自动化项目资产侦察企查产权武器库部署网络空间

020-信息打点-红蓝队自动化项目&资产侦察&企查产权&武器库部署&网络空间 #知识点&#xff1a; 1、工具项目-红蓝队&自动化部署 2、工具项目-自动化侦查收集提取 3、工具项目-综合&网络空间&信息 演示案例&#xff1a; ➢自动化-武器库部署-F8x ➢自…

时间序列预测 — CNN-LSTM-Attention实现多变量负荷预测(Tensorflow):多变量滚动

专栏链接&#xff1a;https://blog.csdn.net/qq_41921826/category_12495091.html 专栏内容 ​ 所有文章提供源代码、数据集、效果可视化 ​ 文章多次上领域内容榜、每日必看榜单、全站综合热榜 ​ ​ ​ ​ ​ ​ ​ 时间序列预测存在的问题 ​ 现有的大量方法没有真正的预测未…

为什么电脑降价了?

周末&#xff0c;非常意外地用不到3000元买到了一款2023年度发布的华为笔记本I5,16G,500G&#xff0c;基本是主流配置&#xff0c;我非常意外&#xff0c;看了又看&#xff0c;不是什么Hwawii&#xff0c;或者Huuawe。然后也不是二手。为什么呢&#xff1f;因为在ALU和FPU之外&…

【ARM 嵌入式 编译系列 7.3 -- GCC 链接脚本中 DISCARD 与 .ARM.exidx】

请阅读【嵌入式开发学习必备专栏 之 ARM GCC 编译专栏】 文章目录 背景.ARM.exidx方法一:使用链接器脚本方法二:使用链接器选项注意事项背景 在移植 RT-Thread 到 cortex-m33(RA4M2)上的时候,在编译的时候遇到下面问题: Building target: ra4m2.elf arm