App下載

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

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

最近有很多人都在說有關(guān)于react的原理是什么這個(gè)話題,那么今天我們就對“react渲染原理是什么”這個(gè)問題來進(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>
  );
}

在這個(gè)代碼中我們用的語法被稱為 JSX,它是?React.createElement?方法的語法糖,我們通過使用 JSX 可以直觀的展現(xiàn) UI 及交互可以實(shí)現(xiàn)關(guān)注點(diǎn)分離,而且每一個(gè)?react?組價(jià)的頂部都要導(dǎo)入React,因?yàn)?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()?接收三個(gè)參數(shù),在代碼中我們可以知道它分別是元素類型、屬性值和子元素這三個(gè)值,而且它最終會(huì)生成Virtual DOM,我們現(xiàn)在將?<app/>?組件內(nèi)容打印到我們的控制臺中,如下所示:

app組件獲取值

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


三、createElement()方法精簡版

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

精簡版

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

調(diào)用

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


四、ReactDOM.render

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

?render()?方法:

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

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

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

因?yàn)槭鞘状螔燧d,所以?root?從?container._reactRootContainer?獲取不到值,就會(huì)創(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;
}

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

下面的兩個(gè)構(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;
  // 每個(gè)fiberRoot實(shí)例上都只會(huì)維護(hù)一個(gè)任務(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屬性始終指向第一個(gè)子節(jié)點(diǎn)
  this.child = null;
  // sibling屬性始終指向第一個(gè)兄弟節(jié)點(diǎn)
  this.sibling = null;
  // 表示更新隊(duì)列,例如在常見的setState操作中,會(huì)將需要更新的數(shù)據(jù)存放到updateQueue隊(duì)列中用于后續(xù)調(diào)度
  this.updateQueue = null;
  // 表示當(dāng)前更新任務(wù)的過期時(shí)間,即在該時(shí)間之后更新任務(wù)將會(huì)被完成
  this.expirationTime = NoWork;
}

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

示意圖

五、React Diff 算法

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


總結(jié):

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


1 人點(diǎn)贊