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

PortalComponent

1 )概述

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

2 )源码

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

js 复制代码
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, 进入

    js 复制代码
    // 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

    js 复制代码
    // 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

    js 复制代码
    // 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的调用,在源码的注释中
相关推荐
子兮曰6 小时前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰6 小时前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万6 小时前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝6 小时前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋7 小时前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁7 小时前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95279 小时前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大9 小时前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师9 小时前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学10 小时前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端