• 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 是干什么的,它的这个优先级有什么用呢?

    下期解答!

  • 相关阅读:
    一个matlab小程序:挑出沪市A股年报与一季度报在同一天发布的股票
    AWK
    matlab搜索路径
    从新浪财经上下载交易明细数据并统计每天的买卖笔数(shell 命令行)
    AWK截取字符串
    tar GNU
    工作效率上的错觉(转载)
    matlab双精度浮点数编码及区间覆盖(原创)
    DNS服务器设置(Ubuntu10.04)
    sed
  • 原文地址:https://www.cnblogs.com/JhoneLee/p/9481618.html
Copyright © 2020-2023  润新知