作為
React
的核心技術之一Virtual DOM
,一直披着神秘的面紗。
實際上,Virtual DOM包含:
- Javascript DOM模型樹(VTree),類似文檔節點樹(DOM)
- DOM模型樹轉節點樹方法(VTree -> DOM)
- 兩個DOM模型樹的差異算法(diff(VTree, VTree) -> PatchObject)
- 根據差異操作節點方法(patch(DOMNode, PatchObject) -> DOMNode)
接下來我們分別探討這幾個部分:
VTree
VTree模型非常簡單,基本結構如下:
{
// tag的名字 tagName: 'p', // 節點包含屬性 properties: { style: { color: '#fff' } }, // 子節點 children: [], // 該節點的唯一表示,后面會講有啥用 key: 1 }
所以我們很容易寫一個方法來創建這種樹狀結構,例如React是這么創建的:
// 創建一個div react.createElement('div', null, [ // 子節點img react.createElement('img', { src: "avatar.png", class: "profile" }), // 子節點h3 react.createElement('h3', null, [[user.firstName, user.lastName].join(' ')]) ]);
VTree -> DOM
這方法也不太難,我們實現一個簡單的:
function create(vds, parent) { // 首先看看是不是數組,如果不是數組統一成數組 !Array.isArray(vds) && (vds = [vds]); // 如果沒有父元素則創建個fragment來當父元素 parent = parent || document.createDocumentFragment(); var node; // 遍歷所有VNode vds.forEach(function (vd) { // 如果VNode是文字節點 if (isText(vd)) { // 創建文字節點 node = document.createTextNode(vd.text); // 否則是元素 } else { // 創建元素 node = document.createElement(vd.tag); } // 將元素塞入父容器 parent.appendChild(node); // 看看有沒有子VNode,有孩子則處理孩子VNode vd.children && vd.children.length && create(vd.children, node); // 看看有沒有屬性,有則處理屬性 vd.properties && setProps({ style: {} }, vd.properties, node); }); return parent; }
diff(VTree, VTree) -> PatchObject
差異算法是Virtual DOM的核心,實際上該差異算法是個取巧算法(當然你不能指望用O(n^3)的復雜度來解決兩個樹的差異問題吧),不過能解決Web的大部分問題。
那么React是如何取巧的呢?
- 分層對比
如圖,React僅僅對同一層的節點嘗試匹配,因為實際上,Web中不太可能把一個Component在不同層中移動。
- 基於key來匹配
還記得之前在VTree中的屬性有一個叫key的東東么?這個是一個VNode的唯一識別,用於對兩個不同的VTree中的VNode做匹配的。
這也很好理解,因為我們經常會在Web遇到擁有唯一識別的Component(例如課程卡片、用戶卡片等等)的不同排列問題。
- 基於自定義元素做優化
React提供自定義元素,所以匹配更加簡單。
patch(DOMNode, PatchObject) -> DOMNode
由於diff操作已經找出兩個VTree不同的地方,只要根據計算出來的結果,我們就可以對DOM的進行差異渲染。
擴展閱讀
具體可參考下面兩份代碼實現:
- @Matt-Esch實現的:virtual-dom
- 我們自己做的簡版實現,用於Mobile頁面渲染的:qvd