最近有很多人都在說有關(guān)于react的原理是什么這個(gè)話題,那么今天我們就對(duì)“react渲染原理是什么”這個(gè)問題來進(jìn)行分析,下面是小編分享的相關(guān)的內(nèi)容,希望對(duì)大家有所幫助!
一、JSX
那么首先我們來看一下,簡(jiǎn)單的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)從而來對(duì)語法進(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)容打印到我們的控制臺(tái)中,如下所示:
我們通過截圖可以看到 Virtual DOM 本質(zhì)上是 JS 對(duì)象,所以我們將節(jié)點(diǎn)信息通過鍵值對(duì)的方式存儲(chǔ)起來,同時(shí)使用嵌套來表示節(jié)點(diǎn)間的層級(jí)關(guān)系。然后再使用 VDOM 能夠避免頻繁的進(jìn)行 DOM 操作,同時(shí)也為后面的 ?React Diff
?算法創(chuàng)造了條件。那么我們現(xiàn)在回到我們的createElement()
方法中,來看一下它是如何生產(chǎn) VDOM 的。
三、createElement()方法精簡(jiǎn)版
有關(guān)于createElement()方法精簡(jiǎn)版的代碼截圖如下:
在截圖中,首先我們通過?createElement()
?方法會(huì)先通過遍歷?config
?獲取所有的參數(shù),然后獲取其子節(jié)點(diǎn)以及默認(rèn)的?Props
?的值,然后我們?cè)趯⒅祩鬟f給?ReactElement()
?調(diào)用返回JS對(duì)象。如下所示:
在截圖中值得我們?nèi)プ⒁獾氖?,每個(gè)?react
?組件都會(huì)使用?$$typeof
?來進(jìn)行標(biāo)識(shí),它的值使用了?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
);
},
};
在代碼中的element
和container
我想大家都很熟悉,然而在代碼中的callback
是用來渲染完成后需要執(zhí)行的回調(diào)函數(shù)。
接下來我們?cè)賮砜纯丛摲椒ǖ亩x,代碼如下:
function legacyRenderSubtreeIntoContainer(
parentComponent,
children,
container,
forceHydrate,
callback
) {
let root = container._reactRootContainer;
let fiberRoot;
// 初次渲染
if (!root) {
// 初始化掛載,獲得React根容器對(duì)象
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
除此之外對(duì)于另一個(gè)參數(shù)?forceHydrate
?代表是否是服務(wù)端渲染,因?yàn)樵谶@邊調(diào)用了?render()
?方法為客戶端渲染,所以默認(rèn)為false。
因?yàn)槭鞘状螔燧d,所以?root
?從?container._reactRootContainer
?獲取不到值,就會(huì)創(chuàng)建?FiberRoot
?對(duì)象。而且在?FiberRoot
?對(duì)象創(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
?對(duì)象,并使用?createHostRootFiber
?方法創(chuàng)建?RootFiber
?對(duì)象,使?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)前激活的與之對(duì)應(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)先級(jí)
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)示意圖如下:
對(duì)于react來說并不會(huì)比原生操作的?DOM
?快,但是在大型的應(yīng)用中,我們往往是不需要每次都進(jìn)行重新渲染的,所以這時(shí)候可以讓react通過?VCOM
?以及?diff
?算法能夠值更新必要的?DOM
?。
總結(jié):
以上就是有關(guān)于“react渲染原理是什么?”這個(gè)問題的相關(guān)內(nèi)容,希望對(duì)大家有所幫助,當(dāng)然如果你覺得有更好的認(rèn)識(shí)也可以提出來和大家一同分享,更多與react相關(guān)的課程和學(xué)習(xí)資料我們都可以在W3cschool中進(jìn)行學(xué)習(xí)和了解。