App下載

react渲染原理是什么?渲染原理分析!

猿友 2021-06-25 17:49:20 瀏覽數(shù) (3799)
反饋

最近有很多人都在說有關于react的原理是什么這個話題,那么今天我們就對“react渲染原理是什么”這個問題來進行分析,下面是小編分享的相關的內(nèi)容,希望對大家有所幫助!


一、JSX

那么首先我們來看一下,簡單的React組件,代碼如下:

import React from 'react';

export default function App() {
  return (
    <div className="App">
      <h1>Hello React</h1>
    </div>
  );
}

在這個代碼中我們用的語法被稱為 JSX,它是?React.createElement?方法的語法糖,我們通過使用 JSX 可以直觀的展現(xiàn) UI 及交互可以實現(xiàn)關注點分離,而且每一個?react?組價的頂部都要導入React,因為?JSX?實際上依賴的是?Babel?(@bable/preset-react)從而來對語法進行轉換,最終生成我們需要的?React.createElement?的嵌套語法。下面我們來看下 JSX 轉換渲染后的結果吧,代碼如下:

function App() {
  return React.createElement(
    'div',
    {
      className: 'App',
    },
    React.createElement('h1', null, 'Hello React')
  );
}

    

二、createElement

?createElement()?方法定如下:

React.createElement(type, [props], [...children]);

?createElement()?接收三個參數(shù),在代碼中我們可以知道它分別是元素類型、屬性值和子元素這三個值,而且它最終會生成Virtual DOM,我們現(xiàn)在將?<app/>?組件內(nèi)容打印到我們的控制臺中,如下所示:

app組件獲取值

我們通過截圖可以看到 Virtual DOM 本質上是 JS 對象,所以我們將節(jié)點信息通過鍵值對的方式存儲起來,同時使用嵌套來表示節(jié)點間的層級關系。然后再使用 VDOM 能夠避免頻繁的進行 DOM 操作,同時也為后面的 ?React Diff ?算法創(chuàng)造了條件。那么我們現(xiàn)在回到我們的createElement()方法中,來看一下它是如何生產(chǎn) VDOM 的。


三、createElement()方法精簡版

有關于createElement()方法精簡版的代碼截圖如下:

精簡版

在截圖中,首先我們通過?createElement()?方法會先通過遍歷?config?獲取所有的參數(shù),然后獲取其子節(jié)點以及默認的?Props?的值,然后我們在將值傳遞給?ReactElement()?調(diào)用返回JS對象。如下所示:

調(diào)用

在截圖中值得我們?nèi)プ⒁獾氖?,每個?react?組件都會使用?$$typeof?來進行標識,它的值使用了?Symbol?數(shù)據(jù)結構來確保唯一性。


四、ReactDOM.render

通過上面的步驟,我們得到了VDOM,react通過協(xié)調(diào)算法(reconciliation)去比較更新前后的VDOM,從而找到需要更新的最小操作,來減少多次操作DOM的成本,由于我們遍歷組件樹,當組件越來越大我們的遞歸遍歷成本就會越高所有我們有了下面這種解決方法。

?render()?方法:

ReactDOM.render(element, container[, callback])

這邊的話我們還需要了解?ReactDOM.render?是怎么構建?fiber tree?,其實呢在ReactDOM.render?中實際調(diào)用了legacyRenderSubtreeIntoContainer這個方法,下面是有關的調(diào)用過程,代碼如下:

ReactDOM = {
  render(element, container, callback) {
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback
    );
  },
};

在代碼中的elementcontainer我想大家都很熟悉,然而在代碼中的callback是用來渲染完成后需要執(zhí)行的回調(diào)函數(shù)。

接下來我們再來看看該方法的定義,代碼如下:

function legacyRenderSubtreeIntoContainer(
  parentComponent,
  children,
  container,
  forceHydrate,
  callback
) {
  let root = container._reactRootContainer;
  let fiberRoot;
  // 初次渲染
  if (!root) {
    // 初始化掛載,獲得React根容器對象
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate
    );
    fiberRoot = root._internalRoot;

    // 初始化安裝不需要批量更新,需要盡快完成
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;

    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}

我們可以發(fā)現(xiàn)到,在代碼中因為掛載是?root?,所以我們需要將parentComponent的值設置為null

除此之外對于另一個參數(shù)?forceHydrate?代表是否是服務端渲染,因為在這邊調(diào)用了?render()?方法為客戶端渲染,所以默認為false。

因為是首次掛載,所以?root?從?container._reactRootContainer?獲取不到值,就會創(chuàng)建?FiberRoot?對象。而且在?FiberRoot?對象創(chuàng)建過程中考慮到了服務端渲染的情況,并且函數(shù)之間相互調(diào)用非常多,所以這里直接展示其最終調(diào)用的核心方法,代碼如下所示:

// 創(chuàng)建fiberRoot和rootFiber并相互引用
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
  const root = new FiberRootNode(containerInfo, tag, hydrate);
  if (enableSuspenseCallback) {
    root.hydrationCallbacks = hydrationCallbacks;
  }

  // 創(chuàng)建fiber tree的根節(jié)點,即rootFiber
  const uninitializedFiber = createHostRootFiber(tag);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  initializeUpdateQueue(uninitializedFiber);

  return root;
}

我們從代碼中可以知道,在這個方法中?containerInfo?就是?root?節(jié)點,然而?tag?為?FiberRoot?節(jié)點的標記,這里變?yōu)?LegacyRoot?。而且另外兩個參數(shù)和服務端渲染是有關的,在代碼中這里使用?FiberRootNode?方法創(chuàng)建了?FiberRoot?對象,并使用?createHostRootFiber?方法創(chuàng)建?RootFiber?對象,使?FiberRoot?中的?current?指向?RootFiber?,?RootFiber?的?stateNode?指向?FiberRoot?,從而形成相互引用。

下面的兩個構造函數(shù)是展現(xiàn)出了?fiberRoot?以及?rootFiber?的部分重要的屬性。

FiberRootNode部分屬性,代碼如下:

function FiberRootNode(containerInfo, tag, hydrate) {
  // 用于標記fiberRoot的類型
  this.tag = tag;
  // 指向當前激活的與之對應的rootFiber節(jié)點
  this.current = null;
  // 和fiberRoot關聯(lián)的DOM容器的相關信息
  this.containerInfo = containerInfo;
  // 當前的fiberRoot是否處于hydrate模式
  this.hydrate = hydrate;
  // 每個fiberRoot實例上都只會維護一個任務,該任務保存在callbackNode屬性中
  this.callbackNode = null;
  // 當前任務的優(yōu)先級
  this.callbackPriority = NoPriority;
}

Fiber Node構造函數(shù)的部分屬性代碼如下:

function FiberNode(tag, pendingProps, key, mode) {
  // rootFiber指向fiberRoot,child fiber指向對應的組件實例
  this.stateNode = null;
  // return屬性始終指向父節(jié)點
  this.return = null;
  // child屬性始終指向第一個子節(jié)點
  this.child = null;
  // sibling屬性始終指向第一個兄弟節(jié)點
  this.sibling = null;
  // 表示更新隊列,例如在常見的setState操作中,會將需要更新的數(shù)據(jù)存放到updateQueue隊列中用于后續(xù)調(diào)度
  this.updateQueue = null;
  // 表示當前更新任務的過期時間,即在該時間之后更新任務將會被完成
  this.expirationTime = NoWork;
}

最終生成的fiber tree結構示意圖如下:

示意圖

五、React Diff 算法

對于react來說并不會比原生操作的?DOM?快,但是在大型的應用中,我們往往是不需要每次都進行重新渲染的,所以這時候可以讓react通過?VCOM ?以及?diff?算法能夠值更新必要的?DOM?。


總結:

以上就是有關于“react渲染原理是什么?”這個問題的相關內(nèi)容,希望對大家有所幫助,當然如果你覺得有更好的認識也可以提出來和大家一同分享,更多與react相關的課程和學習資料我們都可以在W3cschool中進行學習和了解。


1 人點贊