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

下期解答!

猜你喜欢

转载自www.cnblogs.com/JhoneLee/p/9481618.html