本篇內容主要講解“react渲染原理怎么實現”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“react渲染原理怎么實現”吧!
一、JSX
那么首先我們來看一下,簡單的React組件,代碼如下:
import React from 'react';
export default function App() {
return (
<div className="App">
<h2>Hello React</h2>
</div>
);
}
在這個代碼中我們用的語法被稱為 JSX,它是React.createElement
方法的語法糖,我們通過使用 JSX 可以直觀的展現 UI 及交互可以實現關注點分離,而且每一個react
組價的頂部都要導入React,因為JSX
實際上依賴的是Babel
(@bable/preset-react)從而來對語法進行轉換,最終生成我們需要的React.createElement
的嵌套語法。下面我們來看下 JSX 轉換渲染后的結果吧,代碼如下:
function App() {
return React.createElement(
'div',
{
className: 'App',
},
React.createElement('h2', null, 'Hello React')
);
}
二、createElement
createElement()
方法定如下:
React.createElement(type, [props], [...children]);
createElement()
接收三個參數,在代碼中我們可以知道它分別是元素類型、屬性值和子元素這三個值,而且它最終會生成Virtual DOM,我們現在將<app/>
組件內容打印到我們的控制臺中
我們通過截圖可以看到 Virtual DOM 本質上是 JS 對象,所以我們將節點信息通過鍵值對的方式存儲起來,同時使用嵌套來表示節點間的層級關系。然后再使用 VDOM 能夠避免頻繁的進行 DOM 操作,同時也為后面的 React Diff
算法創造了條件。那么我們現在回到我們的createElement()
方法中,來看一下它是如何生產 VDOM 的。
三、createElement()方法精簡版
首先我們通過createElement()
方法會先通過遍歷config
獲取所有的參數,然后獲取其子節點以及默認的Props
的值,然后我們在將值傳遞給ReactElement()
調用返回JS對象。
在截圖中值得我們去注意的是,每個react
組件都會使用$$typeof
來進行標識,它的值使用了Symbol
數據結構來確保唯一性。
四、ReactDOM.render
通過上面的步驟,我們得到了VDOM,react通過協調算法(reconciliation)去比較更新前后的VDOM,從而找到需要更新的最小操作,來減少多次操作DOM的成本,由于我們遍歷組件樹,當組件越來越大我們的遞歸遍歷成本就會越高所有我們有了下面這種解決方法。
render()
方法:
ReactDOM.render(element, container[, callback])
這邊的話我們還需要了解ReactDOM.render
是怎么構建fiber tree
,其實呢在ReactDOM.render
中實際調用了legacyRenderSubtreeIntoContainer
這個方法,下面是有關的調用過程,代碼如下:
ReactDOM = {
render(element, container, callback) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback
);
},
};
在代碼中的element
和container
我想大家都很熟悉,然而在代碼中的callback
是用來渲染完成后需要執行的回調函數。
接下來我們再來看看該方法的定義,代碼如下:
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);
}
我們可以發現到,在代碼中因為掛載是root
,所以我們需要將parentComponent
的值設置為null
除此之外對于另一個參數forceHydrate
代表是否是服務端渲染,因為在這邊調用了render()
方法為客戶端渲染,所以默認為false。
因為是首次掛載,所以root
從container._reactRootContainer
獲取不到值,就會創建FiberRoot
對象。而且在FiberRoot
對象創建過程中考慮到了服務端渲染的情況,并且函數之間相互調用非常多,所以這里直接展示其最終調用的核心方法,代碼如下所示:
// 創建fiberRoot和rootFiber并相互引用
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
const root = new FiberRootNode(containerInfo, tag, hydrate);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
// 創建fiber tree的根節點,即rootFiber
const uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
initializeUpdateQueue(uninitializedFiber);
return root;
}
我們從代碼中可以知道,在這個方法中containerInfo
就是root
節點,然而tag
為FiberRoot
節點的標記,這里變為LegacyRoot
。而且另外兩個參數和服務端渲染是有關的,在代碼中這里使用FiberRootNode
方法創建了FiberRoot
對象,并使用createHostRootFiber
方法創建RootFiber
對象,使FiberRoot
中的current
指向RootFiber
,RootFiber
的stateNode
指向FiberRoot
,從而形成相互引用。
下面的兩個構造函數是展現出了fiberRoot
以及rootFiber
的部分重要的屬性。
FiberRootNode部分屬性,代碼如下:
function FiberRootNode(containerInfo, tag, hydrate) {
// 用于標記fiberRoot的類型
this.tag = tag;
// 指向當前激活的與之對應的rootFiber節點
this.current = null;
// 和fiberRoot關聯的DOM容器的相關信息
this.containerInfo = containerInfo;
// 當前的fiberRoot是否處于hydrate模式
this.hydrate = hydrate;
// 每個fiberRoot實例上都只會維護一個任務,該任務保存在callbackNode屬性中
this.callbackNode = null;
// 當前任務的優先級
this.callbackPriority = NoPriority;
}
Fiber Node構造函數的部分屬性代碼如下:
function FiberNode(tag, pendingProps, key, mode) {
// rootFiber指向fiberRoot,child fiber指向對應的組件實例
this.stateNode = null;
// return屬性始終指向父節點
this.return = null;
// child屬性始終指向第一個子節點
this.child = null;
// sibling屬性始終指向第一個兄弟節點
this.sibling = null;
// 表示更新隊列,例如在常見的setState操作中,會將需要更新的數據存放到updateQueue隊列中用于后續調度
this.updateQueue = null;
// 表示當前更新任務的過期時間,即在該時間之后更新任務將會被完成
this.expirationTime = NoWork;
}
最終生成的fiber tree結構
五、React Diff 算法
對于react來說并不會比原生操作的DOM
快,但是在大型的應用中,我們往往是不需要每次都進行重新渲染的,所以這時候可以讓react通過VCOM
以及diff
算法能夠值更新必要的DOM
。
到此,相信大家對“react渲染原理怎么實現”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。