App下載

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

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

最近有很多人都在說有關(guān)于react的原理是什么這個話題,那么今天我們就對“react渲染原理是什么”這個問題來進(jìn)行分析,下面是小編分享的相關(guān)的內(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 及交互可以實(shí)現(xiàn)關(guān)注點(diǎn)分離,而且每一個?react?組價的頂部都要導(dǎo)入React,因為?JSX?實(shí)際上依賴的是?Babel?(@bable/preset-react)從而來對語法進(jìn)行轉(zhuǎn)換,最終生成我們需要的?React.createElement?的嵌套語法。下面我們來看下 JSX 轉(zhuǎn)換渲染后的結(jié)果吧,代碼如下:

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


三、createElement()方法精簡版

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

精簡版

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

調(diào)用

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


四、ReactDOM.render

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

?render()?方法:

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

這邊的話我們還需要了解?ReactDOM.render?是怎么構(gòu)建?fiber tree?,其實(shí)呢在ReactDOM.render?中實(shí)際調(diào)用了legacyRenderSubtreeIntoContainer這個方法,下面是有關(guān)的調(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的值設(shè)置為null

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

因為是首次掛載,所以?root?從?container._reactRootContainer?獲取不到值,就會創(chuàng)建?FiberRoot?對象。而且在?FiberRoot?對象創(chuàng)建過程中考慮到了服務(wù)端渲染的情況,并且函數(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é)點(diǎn),即rootFiber
  const uninitializedFiber = createHostRootFiber(tag);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  initializeUpdateQueue(uninitializedFiber);

  return root;
}

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

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

FiberRootNode部分屬性,代碼如下:

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

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

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

最終生成的fiber tree結(jié)構(gòu)示意圖如下:

示意圖

五、React Diff 算法

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


總結(jié):

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


1 人點(diǎn)贊