React16源码: React中处理hydrate的核心流程源码实现

hydrate


1 )概述

  • hydrate 在react当中不算特别重要, 但是很多时候会用到的一个API
  • 这个 API 它主要作用就是在进入第一次渲染的时候,如果本身 dom 树上面已经有一个dom结构存在
  • 是否可以去利用这一部分已经存在的dom,然后去避免掉在第一次渲染的时候
  • 要去创建很多 dom 节点的一个过程,可以大大的提升第一次渲染的性能
  • 看下 react 什么时候需要去执行 hydrate 什么时候又不需要去执行它
  • 在 ReactDOM.js 中找到 react 里面的两个渲染 API
    • 一个是 hydrate 另外一个是 render
    • 它们接收的参数都是一样的
    • 它们都调用同一个函数 legacyRenderSubtreeIntoContainer
    • 唯一的区别是它们传入的第4个参数不一样

2 )源码

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

hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
   
   
  // TODO: throw or warn if we couldn't hydrate?
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    true,
    callback,
  );
}

render(
  element: React$Element<any>,
  container: DOMContainer,
  callback: ?Function,
) {
   
   
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}

对于 legacyRenderSubtreeIntoContainer 第4个参数 forceHydrate 表示是否强制融合

function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: DOMContainer,
  forceHydrate: boolean,
  callback: ?Function,
) {
   
   
  // TODO: Ensure all entry points contain this check
  invariant(
    isValidContainer(container),
    'Target container is not a DOM element.',
  );

  if (__DEV__) {
   
   
    topLevelUpdateWarnings(container);
  }

  // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  // member of intersection type." Whyyyyyy.
  let root: Root = (container._reactRootContainer: any);
  if (!root) {
   
   
    // Initial mount
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    if (typeof callback === 'function') {
   
   
      const originalCallback = callback;
      callback = function() {
   
   
        const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
        originalCallback.call(instance);
      };
    }
    // Initial mount should not be batched.
    DOMRenderer.unbatchedUpdates(() => {
   
   
      if (parentComponent != null) {
   
   
        root.legacy_renderSubtreeIntoContainer(
          parentComponent,
          children,
          callback,
        );
      } else {
   
   
        root.render(children, callback);
      }
    });
  } else {
   
   
    if (typeof callback === 'function') {
   
   
      const originalCallback = callback;
      callback = function() {
   
   
        const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
        originalCallback.call(instance);
      };
    }
    // Update
    if (parentComponent != null) {
   
   
      root.legacy_renderSubtreeIntoContainer(
        parentComponent,
        children,
        callback,
      );
    } else {
   
   
      root.render(children, callback);
    }
  }
  return DOMRenderer.getPublicRootInstance(root._internalRoot);
}
  • 在调用 legacyCreateRootFromDOMContainer 这个方法的时候,会传入这个 forceHydrate 这个参数
    function legacyCreateRootFromDOMContainer(
      container: DOMContainer,
      forceHydrate: boolean,
    ): Root {
         
         
      // 在这个方法里面,首先它声明了一个参数叫 shouldHydrate,如果 forceHydrate 为 true, shouldHydrate 也一定为 true
      // 不是 true, 需要调用 `shouldHydrateDueToLegacyHeuristic` 来进行判断
      // 这也是目前在 react dom 环境中,即便使用了 render API,但仍然有可能会去执行 hydrate 的一个过程
      // 注意,只针对目前版本
      const shouldHydrate =
        forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
      // First clear any existing content.
      if (!shouldHydrate) {
         
         
        let warned = false;
        let rootSibling;
        while ((rootSibling = container.lastChild)) {
         
         
          if (__DEV__) {
         
         
            if (
              !warned &&
              rootSibling.nodeType === ELEMENT_NODE &&
              (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
            ) {
         
         
              warned = true;
              warningWithoutStack(
                false,
                'render(): Target node has markup rendered by React, but there ' +
                  'are unrelated nodes as well. This is most commonly caused by ' +
                  'white-space inserted around server-rendered markup.',
              );
            }
          }
          container.removeChild(rootSibling);
        }
      }
      if (__DEV__) {
         
         
        if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
         
         
          warnedAboutHydrateAPI = true;
          lowPriorityWarning(
            false,
            'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
              'will stop working in React v17. Replace the ReactDOM.render() call ' +
              'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
          );
        }
      }
      // Legacy roots are not async by default.
      const isConcurrent = false;
      // 后续在创建 ReactRoot 的时候,传入了 shouldHydrate
      return new ReactRoot(container, isConcurrent, shouldHydrate);
    }
    
    • 进入 shouldHydrateDueToLegacyHeuristic
      function shouldHydrateDueToLegacyHeuristic(container) {
             
             
        // 这里面我们拿到了rootElement 之后,我们首先判断的条件
        // 就是它是否存在, 以及它是否是一个普通的节点
        // 以及它是否有 `ROOT_ATTRIBUTE_NAME` 这个attribute
        // ROOT_ATTRIBUTE_NAME 的 值是 data-reactroot
        // 这个是在调用服务端渲染的API的时候,生成的HTML上面它的container节点上面会增加这个attribute
        // 这也是react服务端渲染的API帮助我们去在客户端去识别是否需要融合节点的一个帮助的attribute
        // 如果符合这个条件,React它就会猜测, 即便你调用的不是 Hydrate API
        // 它仍然猜测你是需要执行 hydrate,它的 shouldHydrate 就等于 true
        const rootElement = getReactRootElementInContainer(container);
        return !!(
          rootElement &&
          rootElement.nodeType === ELEMENT_NODE &&
          rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
        );
      }
      
    • 进入 getReactRootElementInContainer
      function getReactRootElementInContainer(container: any) {
             
             
        if (!container) {
             
             
          return null;
        }
        // DOCUMENT_NODE 表示 window.document, 返回 documentElement 就是 html节点
        if (container.nodeType === DOCUMENT_NODE) {
             
             
          return container.documentElement;
        } else {
             
             
          return container.firstChild;
        }
      }
      
      • 这也是强烈不推荐在调用 render API 的时候传入 document 的原因
      • 因为如果传入了document,那么它就会对整个HTML文档来进行一个调和的过程
      • 如果我不需要一个融合的过程,它可能会直接把HTML里面所有的内容都删掉,那么这就很可能会导致一些问题了
      • 所以在这里面,如果是你传入的是 document,它返回的是 documentElement,那就是HTML节点
      • 如果它传入的不是 document, 就是这个 container.firstChild 节点
        • 比如说传入的是root节点,root节点如果它没有子节点,它的firstChild 肯定就是一个 null这种情况
        • 这时候肯定不需要融合的一个过程
    • 进入 ReactRoot
      function ReactRoot(
        container: Container,
        isConcurrent: boolean,
        hydrate: boolean,
      ) {
             
             
        const root = DOMRenderer.createContainer(container, isConcurrent, hydrate);
        this._internalRoot = root;
      }
      
      • 进入 createContainer
        export function createContainer(
          containerInfo: Container,
          isConcurrent: boolean,
          hydrate: boolean,
        ): OpaqueRoot {
                 
                 
          return createFiberRoot(containerInfo, isConcurrent, hydrate);
        }
        
        • 进入 createFiberRoot
          export function createFiberRoot(
            containerInfo: any,
            isConcurrent: boolean,
            hydrate: boolean,
          ): FiberRoot {
                     
                     
            // Cyclic construction. This cheats the type system right now because
            // stateNode is any.
            const uninitializedFiber = createHostRootFiber(isConcurrent);
          
            let root;
            if (enableSchedulerTracing) {
                     
                     
              root = ({
                     
                     
                
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值