加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

React 16 源码瞎几把解读 【三 点 一】 把react组件对象弄到dom

发布时间:2020-12-15 20:25:10 所属栏目:百科 来源:网络整理
导读:一、ReactDOM.render 都干啥了 我们在写react的时候,最后一步肯定是 ReactDOM.render( div Home name="home"/ /div ,document.getElementById( ‘app‘ )); ? 我们上面得知jsx被解析成了虚拟dom对象,我们把一个对象和一个dom传入render方法就得到了我们的

一、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,);
      } 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,);
    } 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,hydrate);
 8   // root 实际上就和fiber有了联系
 9   const root = DOMRenderer.createContainer(container,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,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,// 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,// 可有可无
 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,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 是干什么的,它的这个优先级有什么用呢?

?

下期解答!

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读