React16源码: React中的PortalComponent创建, 调和, 更新的源码实现

发布时间:2024年01月20日

PortalComponent


1 )概述

  • React Portal之所以叫Portal,因为做的就是和“传送门”一样的事情
  • render到一个组件里面去,实际改变的是网页上另一处的DOM结构
  • 主要关注 portal的创建, 调和, 更新过程

2 )源码

定位到 packages/react-dom/src/client/ReactDOM.js#L576

function createPortal(
  children: ReactNodeList,
  container: DOMContainer,
  key: ?string = null,
) {
  invariant(
    isValidContainer(container),
    'Target container is not a DOM element.',
  );
  // TODO: pass ReactDOM portal implementation as third argument
  return ReactPortal.createPortal(children, container, null, key);
}

  • 这里调用的是 ReactPortal.createPortal, 进入

    // packages/shared/ReactPortal.js#L14
    export function createPortal(
      children: ReactNodeList,
      containerInfo: any,
      // TODO: figure out the API for cross-renderer implementation.
      implementation: any,
      key: ?string = null,
    ): ReactPortal {
      return {
        // This tag allow us to uniquely identify this as a React Portal
        $$typeof: REACT_PORTAL_TYPE,
        key: key == null ? null : '' + key,
        children,
        containerInfo, // dom 挂载节点
        implementation,
      };
    }
    
    • 这里返回一个对象,类似于 ReactElement
    • 区别在于 $$typeofcontainerInfo 需要的挂载点
  • 对于 REACT_PORTAL_TYPE 类型的组件在 reconcile 时, 看下具体操作, 找到 reconcileSinglePortal

    // packages/react-reconciler/src/ReactChildFiber.js#L1171
    function reconcileSinglePortal(
      returnFiber: Fiber,
      currentFirstChild: Fiber | null,
      portal: ReactPortal,
      expirationTime: ExpirationTime,
    ): Fiber {
      const key = portal.key; // 当前的 key
      let child = currentFirstChild;
      // 如果 存在 child, 对比 child.key === key
      while (child !== null) {
        // TODO: If key === null and child.key === null, then this only applies to
        // the first item in the list.
        if (child.key === key) {
          // 关注这里,有 containerInfo 的对比,portal 需要关心渲染到的节点是否有变化
          // 如果节点有变化,那么这个 portal 的渲染过程也会有变化
          // 都符合,说明老节点都可以复用
          if (
            child.tag === HostPortal &&
            child.stateNode.containerInfo === portal.containerInfo &&
            child.stateNode.implementation === portal.implementation
          ) {
            deleteRemainingChildren(returnFiber, child.sibling); // 删除其他节点
            // 通过 useFiber 复用这个节点
            const existing = useFiber(
              child,
              portal.children || [],
              expirationTime,
            );
            existing.return = returnFiber;
            return existing;
          } else {
            // key相同,但是不符合上述if, 没法复用,删除干净
            deleteRemainingChildren(returnFiber, child);
            break;
          }
        } else {
          // key 不同,删除当前节点
          deleteChild(returnFiber, child);
        }
        child = child.sibling;
      }
    
      // 创建一个 portal 的 fiber 对象
      const created = createFiberFromPortal(
        portal,
        returnFiber.mode,
        expirationTime,
      );
      created.return = returnFiber;
      return created;
    }
    
  • 对于更新一个portal节点,进入 updatePortalComponent

    // packages/react-reconciler/src/ReactFiberBeginWork.js#L1322
    function updatePortalComponent(
      current: Fiber | null,
      workInProgress: Fiber,
      renderExpirationTime: ExpirationTime,
    ) {
      // 挂载点处理
      pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
      const nextChildren = workInProgress.pendingProps; // 这里 pendingProps 是 children
      if (current === null) {
        // Portals are special because we don't append the children during mount
        // but at commit. Therefore we need to track insertions which the normal
        // flow doesn't do during mount. This doesn't happen at the root because
        // the root always starts with a "current" with a null child.
        // TODO: Consider unifying this with how the root works.
        workInProgress.child = reconcileChildFibers(
          workInProgress,
          null,
          nextChildren,
          renderExpirationTime,
        );
      } else {
        reconcileChildren(
          current,
          workInProgress,
          nextChildren,
          renderExpirationTime,
        );
      }
      return workInProgress.child;
    }
    
    • 之前梳理过的 API 不再赘述
    • 一些API的调用,在源码的注释中
文章来源:https://blog.csdn.net/Tyro_java/article/details/135722124
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。