react和reactdom

react和reactdom有什么區別

ReactDom 只做和瀏覽器或DOM相關的操作,例如:ReactDOM.render() 和 ReactDOM.findDOMNode()。如果是服務器端渲染,可以 ReactDOM.renderToString()。

React 不僅能通過 ReactDOM 和Web頁面打交道,還能用在服務器端SSR,移動端ReactNative和桌面端Electron。

React 在v0.14之前是沒有 ReactDOM 的,所有功能都包含在 React 里。從v0.14(2015-10)開始,React 才被拆分成React 和 ReactDOM。為什么要把 React 和 ReactDOM 分開呢?因為有了 ReactNative。React 只包含了 Web 和 Mobile 通用的核心部分,負責 Dom 操作的分到 ReactDOM 中,負責 Mobile 的包含在 ReactNative 中。

ReactDom是React的一部分。ReactDOM是React和DOM之間的粘合劑,一般用來定義單一的組件,或者結合ReactDOM.findDOMNode()來使用。更重要的是ReactDOM包已經允許開發者刪除React包添加的非必要的代碼,并將其移動到一個更合適的存儲庫。

react渲染組件方式

ReactDOM.render()適用于React應用的根組件渲染,ReactDOM.createPortal()適用于將組件渲染到DOM樹中的任意位置,而使用React-Portal庫可以更方便地實現ReactDOM.createPortal()的功能。

ReactDOM.render()做了什么

ReactDOM.render函數是整個 React 應用程序首次渲染的入口函數,它的參數是什么,返回值是什么,函數內部做了什么?
ReactDOM.render(<App />, document.getElementById("root"));
首先看下首次渲染時候,函數調用棧。

image.png

render函數源碼
源碼地址

export function render(
  element: React$Element<any>, // 要渲染的組件
  container: Container, // 根容器
  callback: ?Function // 回調函數
) {

  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback
  );
}

legacyRenderSubtreeIntoContainer函數
源碼地址
內部實現:

判斷是否為第一次渲染,也就是首次掛載
首次掛載,首先會創建FiberRoot,然后判斷是否有回調函數,有就執行,最后進入updateContainer
非首次,獲取FiberRoot,然后判斷是否有回調函數,有就執行,最后進入updateContainer
首次和非首次,最大的區別在于是否創建FiberRoot和走不走批量更新。
最后返回當前應用程序根組件的實例(getPublicRootInstance

function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function,
) {
  let root: RootType = (container._reactRootContainer: any);
  let fiberRoot;

  // 初始化也就是react, 第一次渲染
  if (!root) {
    // Initial mount

    /**
     * 首次掛載時,會通過 legacyCreateRootFromDOMContainer 方法創建 container._reactRootContainer 對象并賦值給 root
     */
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );

    // 也就是ReactDOM.render時候, 產生了fiberRoot
    fiberRoot = root._internalRoot;

    /**
     * 判斷是否存在callback, 也就是ReactDom.render中的第三個參數
     */
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        // 通過實例去調用originalCallback方法
        originalCallback.call(instance);
      };
    }
    // 初始化掛載的時候, 不應該批量更新
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // Update
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}

legacyCreateRootFromDOMContainer函數
legacyCreateRootFromDOMContainer ===> 。。。 ===> createFiberRoot。

前面調用了一大堆函數,實際上最終目的就是創建Fiber,根據前序的截圖來看,最終會去調用createFiberRoot。

首先創建一個FiberRootNode
然后創建RootFiber
接著將兩者關聯起來
接著初始化隊列updateQueue
最后返回FiberRootNode

  fiberRootNode.current = rootFiber;
  rootFiber.stateNode = fiberRootNode;

源碼地址
createFiberRoot源碼

export function createFiberRoot(
  containerInfo: any,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
  // 創建fiberRoot(fiberRootNode)
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);

  if (enableSuspenseCallback) {
    root.hydrationCallbacks = hydrationCallbacks;
  }
  const uninitializedFiber = createHostRootFiber(tag);// 創建rootFiber(fiberNode)

  // 把rootFiber掛載到fiberRoot的current屬性上  
  root.current = uninitializedFiber;

  // 把fiberRoot掛載到rootFiber的stateNode屬性上
  uninitializedFiber.stateNode = root;

  // 初始化更新隊列
  initializeUpdateQueue(uninitializedFiber);

  return root;
}

updateContainer函數

源碼地址

這個函數主要是計算當前節點的lane優先級,創建update對象,加入更新隊列,最后被調度。這三個知識點不屬于本文所細講,留個印象,后面再細講。

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function
): Lane {
  // 獲取fiberRoort.current, 也就是rootFiber, Fiber樹的頭結點
  const current = container.current;

  /* 當前觸發更新時候的時間戳 */
  const eventTime = requestEventTime();

  // console.log(eventTime, performance.now())

  // 計算當前節點lane(優先級)
  const lane = requestUpdateLane(current);

  if (enableSchedulingProfiler) {
    markRenderScheduled(lane);
  }

  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  /**
   * 根據lane(優先級)計算當前節點的update對象
   */
  const update = createUpdate(eventTime, lane);
  // Caution: React DevTools currently depends on this property
  // being called "element".
  update.payload = { element };

  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    update.callback = callback;
  }

  // 將update對象入隊
  enqueueUpdate(current, update);

  // 調度當前的Fiber節點(也就是rootFiber)
  scheduleUpdateOnFiber(current, lane, eventTime);

  return lane;
}

解答開頭提到的三個問題

1.參數是什么:
要渲染的子組件
根容器
要執行的回調函數

2.返回值是什么:
當前應用程序根組件的實例

3.做了什么操作:
創建FiberRoot,計算lane優先級,創建update對象,加入更新隊列,最后被調度器調度更新。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容