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 是干什么的,它的這個優先級有什么用呢?

 

下期解答!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM