zoukankan      html  css  js  c++  java
  • React 16 源码瞎几把解读 【三 点 一】 把react组件对象弄到dom中去(矛头指向fiber,fiber不解读这个过程也不知道)

    一、ReactDOM.render 都干啥了

    我们在写react的时候,最后一步肯定是

    ReactDOM.render(
        <div>
            <Home name="home"/>
        </div>
        ,
        document.getElementById('app')
    );

    我们上面得知jsx被解析成了虚拟dom对象,我们把一个对象和一个dom传入render方法就得到了我们的页面,好神奇呀,我们开始撸到render方法:

    const ReactDOM: Object = {
    
      render(
        element: React$Element<any>,  // react组件对象
        container: DOMContainer,   // 就是id为app的那个dom
        callback: ?Function, // callback 没有
      ) {
        return legacyRenderSubtreeIntoContainer(
          null,
          element,
          container,
          false,
          callback,
        );
      }
    
    }

    抛开typeScript那些恶心的类型限定不谈,我们发现render的实质就是调用并返回   legacyRenderSubtreeIntoContainer   这个函数执行后的结果,你看这个函数的命名:

    legacy : 遗产  +  render: 渲染  +  subtree: 子树  +  into: 到 +  container: 容器

    爱几把咋翻译咋翻译,大致意思就是说把 虚拟的dom树渲染到真实的dom容器中。此函数应当荣当 核心函数 宝座

    二、legacyRenderSubtreeIntoContainer 又干了啥?

    还是撸到丫的源码:

    function legacyRenderSubtreeIntoContainer(
      parentComponent: ?React$Component<any, any>,  // null
      children: ReactNodeList, // element 虚拟dom树  
      container: DOMContainer, // html中的dom根对象
      forceHydrate: boolean, // false 服务器端渲染标识
      callback: ?Function, // 回调函数  没有
    ) {
      // 对container进行校验
      invariant(
        isValidContainer(container),
        'Target container is not a DOM element.',
      );
      // 取root对象,一般如果非服务器端渲染这个root是不存在的
      let root: Root = (container._reactRootContainer: any);
      // 进入浏览器端渲染流程
      if (!root) {
        //  人工制造root,附加了一堆fiber的东西到_reactRootContainer
        root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
          container,
          forceHydrate,
        );
    
        if (typeof callback === 'function') {
          const originalCallback = callback;
          callback = function() {
            // 该变callback的this为 instance
            const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
            originalCallback.call(instance);
          };
        }
        DOMRenderer.unbatchedUpdates(() => {
          if (parentComponent != null) {
            // 向真实dom中挂载虚拟dom
            root.legacy_renderSubtreeIntoContainer(
              parentComponent,
              children,
              callback,
            );
          } else {
            // 多么直白的语义
            root.render(children, callback);
          }
        });
      } else {
        // 还是先整一下callback
        if (typeof callback === 'function') {
          const originalCallback = callback;
          callback = function() {
            const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
            originalCallback.call(instance);
          };
        }
        // 还是上面那一套
        if (parentComponent != null) {
          // 向root中挂载dom
          root.legacy_renderSubtreeIntoContainer(
            parentComponent,
            children,
            callback,
          );
        } else {
          root.render(children, callback);
        }
      }
      // 返回container 中的dom
      return DOMRenderer.getPublicRootInstance(root._internalRoot);
    }

    通过看这个核心函数的代码,发现它其中有3个谜团:

    1. _reactRootContainer 的制造:legacyCreateRootFromDOMContainer

       这个函数会制造一个对象挂载到真实的dom根节点上,有了这个对象,执行该对象上的一些方法可以将虚拟dom变成dom树挂载到根节点上

    2. DOMRenderer.unbatchedUpdates

       它的回调执行了挂载dom结构的方法

    3.  root.legacy_renderSubtreeIntoContainer 和 root.render

       如果有parentComponent 这个东西,就执行root.render 否则 root.legacy_renderSubtreeIntoContainer

    三、跟进谜团

    1.root的制造

    找到 legacyCreateRootFromDOMContainer 函数:

     1 function legacyCreateRootFromDOMContainer(
     2   container: DOMContainer,
     3   forceHydrate: boolean, // false
     4 ): Root {
     5   const shouldHydrate =
     6     forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
     7   // 是否需要服务器端渲染
     8   if (!shouldHydrate) {
     9     let warned = false;
    10     let rootSibling;
    11     while ((rootSibling = container.lastChild)) {
    12       if (__DEV__) {
    13         ...
    14       }
    15       // 将dom根节点清空
    16       container.removeChild(rootSibling);
    17     }
    18   }
    19   if (__DEV__) {
    20     ...
    21   }
    22   const isAsync = false;
    23   return new ReactRoot(container, isAsync, shouldHydrate);
    24 }
    View Code

    我们发现实际上该函数做的只是在非ssr的情况下,将dom根节点清空,然后返回一个new ReactRoot(...)

    那么重点又跑到了ReactRoot中:

     1 // 构造函数
     2 function ReactRoot(
     3   container: Container,  // 被清空的dom根节点
     4   isAsync: boolean,  // false
     5   hydrate: boolean // false
     6 ) {
     7   // 追查之后发现:createFiberRoot(containerInfo, isAsync, hydrate);
     8   // root 实际上就和fiber有了联系
     9   const root = DOMRenderer.createContainer(container, isAsync, hydrate);
    10   this._internalRoot = root;
    11 }
    12 
    13 
    14 // 原型方法
    15 
    16 // 渲染
    17 ReactRoot.prototype.render = function(
    18   children: ReactNodeList,
    19   callback: ?() => mixed,
    20 ): Work {
    21   const root = this._internalRoot;
    22   const work = new ReactWork();
    23   callback = callback === undefined ? null : callback;
    24   
    25   if (callback !== null) {
    26     work.then(callback);
    27   }
    28   DOMRenderer.updateContainer(children, root, null, work._onCommit);
    29   return work;
    30 };
    31 
    32 // 销毁组件
    33 ReactRoot.prototype.unmount = function(callback: ?() => mixed): Work {
    34     ...
    35 };
    36 
    37 
    38 ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
    39   parentComponent: ?React$Component<any, any>,
    40   children: ReactNodeList,
    41   callback: ?() => mixed,
    42 ): Work {
    43   const root = this._internalRoot;
    44   const work = new ReactWork();
    45   callback = callback === undefined ? null : callback;
    46   if (callback !== null) {
    47     work.then(callback);
    48   }
    49   DOMRenderer.updateContainer(children, root, parentComponent, work._onCommit);
    50   return work;
    51 };
    52 
    53 ReactRoot.prototype.createBatch = function(): Batch {
    54     .....
    55 };
    View Code

    通过以上代码我们就了解到root到底是个啥玩意儿,这个root有render等方法外,同时还附加了一个和fiber相关的  _internalRoot属性。

    由此可知,不管是root.render 还是 root.legacy_renderSubtreeIntoContainer  都会去执行  DOMRenderer.updateContainer方法,无非就是传入的参数时:第三个参数传什么 而已。

    2.DOMRenderer.unbatchedUpdates干了什么

     1 // 正在批量更新标识
     2 let isBatchingUpdates: boolean = false;
     3 // 未批量更新标识
     4 let isUnbatchingUpdates: boolean = false;
     5 // 非批量更新操作
     6 function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
     7   // 如果正在批量更新
     8   if (isBatchingUpdates && !isUnbatchingUpdates) {
     9     // 未批量更新设为true
    10     isUnbatchingUpdates = true;
    11     try {
    12       // 运行入参函数且返回执行结果
    13       return fn(a);
    14     } finally {
    15       // 仍旧将未批量更新设为false
    16       isUnbatchingUpdates = false;
    17     }
    18   }
    19   // 不管是否在批量更新流程中,都执行入参函数
    20   return fn(a);
    21 }
    View Code

    记住这里两个十分重要的标识:isBatchingUpdates  和  isUnbatchingUpdates    初始值都是false

     由此可知 unbatchedUpdates 无论如何都会执行入参函数,无非在批量更新的时候多一些流程控制。这里留坑

    3. root.legacy_renderSubtreeIntoContainer 和 root.render 在弄什么?

    通过上面的层层扒皮,无论怎样判断,最终都会到以上两个方法中,而这两个方法的核心就是 DOMRenderer.updateContainer,无非就是传不传父组件而已

    传入的参数有: 1:虚拟dom对象树   2:之前造出来的root中和fiber相关的_internalRoot  3.父组件(null 或 父组件)  4.回调函数

      1 export function updateContainer(
      2   element: ReactNodeList,  // 虚拟dom对象
      3   container: OpaqueRoot, // 被制造出来的fiber root
      4   parentComponent: ?React$Component<any, any>, // null
      5   callback: ?Function, //没传
      6 ): ExpirationTime {
      7   // 还记得虚拟dom对象
      8   const current = container.current; 
      9   const currentTime = requestCurrentTime();
     10   const expirationTime = computeExpirationForFiber(currentTime, current); // 计算优先级
     11   return updateContainerAtExpirationTime(
     12     element,
     13     container,
     14     parentComponent,
     15     expirationTime,
     16     callback,
     17   );
     18 }
     19 // 剥皮
     20 
     21 // 根据渲染优先级更新dom
     22 export function updateContainerAtExpirationTime(
     23   element: ReactNodeList, // 虚拟dom对象
     24   container: OpaqueRoot, // 和fiber相关的_internalRoot
     25   parentComponent: ?React$Component<any, any>, // 可有可无
     26   expirationTime: ExpirationTime, // 计算出来的渲染优先级
     27   callback: ?Function, // 回调函数,没有
     28 ) {
     29   const current = container.current;
     30 
     31   if (__DEV__) {
     32     ...
     33   }
     34   // 获取到父组件内容
     35   const context = getContextForSubtree(parentComponent);
     36   // 赋值操作,不知道干啥用
     37   if (container.context === null) {
     38     container.context = context;
     39   } else {
     40     container.pendingContext = context;
     41   }
     42   // 又到了下一站:schedule:安排,  Root: 根, Update:更新
     43   return scheduleRootUpdate(current, element, expirationTime, callback);
     44 }
     45 // 剥皮
     46 
     47 // 安排根节点更新
     48 function scheduleRootUpdate(
     49   current: Fiber, // fiber对象
     50   element: ReactNodeList, // 虚拟dom树
     51   expirationTime: ExpirationTime, // 更新优先级
     52   callback: ?Function, // 回调
     53 ) {
     54   if (__DEV__) {
     55     ...
     56   }
     57   /*
     58     export const UpdateState = 0;
     59     export function createUpdate(expirationTime: ExpirationTime): Update<*> {
     60         return {
     61           expirationTime: expirationTime,
     62         
     63           tag: UpdateState,
     64           payload: null,
     65           callback: null,
     66 
     67           next: null,
     68           nextEffect: null,
     69         };
     70     }
     71   */
     72 
     73   // 返回一个包含以上属性的update对象
     74   const update = createUpdate(expirationTime);
     75   // 将虚拟dom树放入payload 
     76   update.payload = {element};
     77 
     78   callback = callback === undefined ? null : callback;
     79   if (callback !== null) {
     80     warningWithoutStack(
     81       typeof callback === 'function',
     82       'render(...): Expected the last optional `callback` argument to be a ' +
     83         'function. Instead received: %s.',
     84       callback,
     85     );
     86     update.callback = callback;
     87   }
     88   // 开始加入更新队列了,又得剥皮
     89   enqueueUpdate(current, update);
     90   // 
     91   scheduleWork(current, expirationTime);
     92   return expirationTime;
     93 }
     94 
     95 
     96 // 更新队列
     97 // 核心update
     98 export function enqueueUpdate<State>(
     99   fiber: Fiber, 
    100   update: Update<State> // 上文那个update对象
    101 ) {
    102   // 根据fiber的指示进行更新
    103   ...
    104 }
    View Code

    根据一系列的剥皮,最终指向了enqueueUpdate 这个函数,而这个函数和fiber是紧密耦合的,fiber是一个棘手的问题,不理解fiber就无法弄清虚拟dom如何更新到真实dom中。

    预知fiber如何,且听后续分晓!!!

    四、本次的坑有以下几个:

    1. _internalRoot 如何制造出来的,丫有什么作用,为什么后面的函数参数都传它

    2. enqueueUpdate 跟fiber的关系还不清不楚

    3. expirationTime 是干什么的,它的这个优先级有什么用呢?

    下期解答!

  • 相关阅读:
    Atitti 图像处理 图像混合 图像叠加 blend 原理与实现
    Atitit Gaussian Blur 高斯模糊 的原理and实现and 用途
    Atitit 图像处理 灰度图片 灰度化的原理与实现
    Atitit (Sketch Filter)素描滤镜的实现  图像处理  attilax总结
    Atitit 实现java的linq 以及与stream api的比较
    Atitit attilax在自然语言处理领域的成果
    Atitit 图像处理 常用8大滤镜效果 Jhlabs 图像处理类库 java常用图像处理类库
    Atitit 图像处理--图像分类 模式识别 肤色检测识别原理 与attilax的实践总结
    Atitit apache 和guava的反射工具
    atitit。企业的价值观 员工第一 vs 客户第一.docx
  • 原文地址:https://www.cnblogs.com/JhoneLee/p/9481618.html
Copyright © 2011-2022 走看看