生成virtual DOM
樹
bable轉換jsx的結果
let style = { border: "1px solid orange", margin: "5px" }
let element = (
<div id="A1" style={style}> A1
<div id="B1" style={style}> B1
<div id="C1" style={style}>C1</div>
<div id="C2" style={style}>C2</div>
</div>
<div id="B2" style={style}>B2</div>
</div>
)
//babel解析結果:
// React.createElement("div", { id: "A1", style: style },
// " A1",
// React.createElement("div", { id: "B1", style: style },
// " B1",
// React.createElement("div", { id: "C1", style: style }, "C1"),
// React.createElement("div", { id: "C2", style: style }, "C2")
// ),
// React.createElement("div", { id: "B2", style: style }, "B2")
// );
實現createElment
import { ELEMENT_TEXT } from './constants'
function createElement(type, props, ...children) {
delete props.__source;
delete props.__self;
return {
type,
props: {
...props,
children: children.map(child => {
/* 文本節點特殊處理 */
return typeof child === 'object' ? child : {
type: ELEMENT_TEXT,
props: { text: child, children: [] }
}
})
}
}
}
React.createElement處理過後的virtual DOM
實現render方法
render使用
ReactDOM.render(
element,
document.getElementById('root')
);
react-dom入口文件
import { TAG_ROOT } from "../react/constants"
import scheduleRoot from './schedule'
function render(element, container) {
/* 根fiber */
let rootFiber = {
tag: TAG_ROOT,
stateNode: container,
props: { children: [element] }
};
scheduleRoot(rootFiber);
}
export default { render}
1. 初始化
let wrokProgressRoot = null;//記錄根節點
let nextUnitOfWork = null;//記錄當前工作單元
function scheduleRoot(rootFiber) {
wrokProgressRoot = rootFiber;
nextUnitOfWork = rootFiber;
}
2. 工作循環
requestIdleCallback
作用
實現在瀏覽器空閒時運行workLoop
,若超過500ms之不管是否空閒都運行(60幀顯示器每16.6ms刷新一次,存在空閒時間則執行performUnitOfwork
,一直到達500ms無空閒時間則強制執行)
// 工作循環
function workLoop(deadline) {
// 時間片未到
if ((deadline.timeout || deadline.timeRemaining() > 0) && nextUnitOfWork) {
/* 執行工作單元,返回下一單元 */
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
/* 還有工作單元未執行 */
if (!nextUnitOfWork && wrokProgressRoot) {
console.log("Render 完成");
// 生成DOM
commitRoot();
}
requestIdleCallback(workLoop, { timeout: 500 });
}
requestIdleCallback(workLoop, { timeout: 500 });
2.1 performUnitOfWork執行工作單元
function performUnitOfWork(currentFiber) {
//構建
beginWork(currentFiber);
/* 子元素 */
if (currentFiber.child) {
return currentFiber.child;
}
/* 沒有子元素釋放自己並往上找 */
while (currentFiber) {
/* fiber子元素全部完成,將自身合併到副作用鏈 */
completeUnitOfWork(currentFiber);
if (currentFiber.sibling) {
return currentFiber.sibling;
}
/* 往上找 */
currentFiber = currentFiber.return;
}
}
2.1.1 beginWork分類構建
function beginWork(currentFiber) {
//根元素
if (currentFiber.tag === TAG_ROOT) {
upDateRoot(currentFiber);
}
//原生節點
if (currentFiber.tag === TAG_HOST) {
upDateHost(currentFiber);
}
//文本節點
if (currentFiber.tag === TAG_TEXT) {
upDateText(currentFiber);
}
}
/* 更新根元素 */
function upDateRoot(currentFiber) {
/* 構建子元素 */
let newChildren = currentFiber.props.children;
reconcileChildren(currentFiber, newChildren);
}
/* 更新原生元素 */
function upDateHost(currentFiber) {
/* 創建DOM */
if (!currentFiber.stateNode) {
currentFiber.stateNode = createDOM(currentFiber);
}
/* 獲取並構建子元素 */
let newChildren = currentFiber.props.children;
reconcileChildren(currentFiber, newChildren);
}
/* 更新文本元素 */
function upDateText(currentFiber) {
if (!currentFiber.stateNode) {
currentFiber.stateNode = createDOM(currentFiber);
}
}
createDOM方法創建Dom,設置屬性
/* 創建DOM */
function createDOM(currentFiber) {
if (currentFiber.tag === TAG_TEXT) {
/* 直接船艦文本節點 */
return document.createTextNode(currentFiber.props.text);
}
else if (currentFiber.tag === TAG_HOST) {
let stateNode = document.createElement(currentFiber.type);//<div></div>
setProps(stateNode, {}, currentFiber.props);
//<div id="A1" style="border: 1px solid orange; margin: 5px;"></div>
return stateNode;
}
}
reconcileChildren遍歷子節點並記錄父子兄弟fiber關係
/* 創建子元素fiber並連接到父元素上 */
function reconcileChildren(returnFiber, newChildren) {
let newChildIndex = 0,prevSibling = null;
//遍歷returnFiber的子節點
while (newChildIndex < newChildren.length) {
let newChild = newChildren[newChildIndex];
/* 標識文本和原生組件 */
let tag;
if (newChild.type === ELEMENT_TEXT) {
tag = TAG_TEXT;
} else if (typeof newChild.type === "string") {
tag = TAG_HOST;
}
/* 創建Fiber */
let newFiber = {
tag,
type: newChild.type,
props: newChild.props,
stateNode: null,
return: returnFiber,//父節點
effectTag: PLACEMENT,//操作
nextEffect: null//下一個節點
}
/* 連接Fiber,第一個子元素作爲兒子,其他作爲兄弟連接 */
if (newChildIndex === 0) {
returnFiber.child = newFiber;
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
newChildIndex++;
}
}
2.1.2 completeUnitOfWork構建副作用鏈
function completeUnitOfWork(currentFiber) {
/* 獲取父節點 */
let returnFiber = currentFiber.return;
if (returnFiber) {
/* 將自己連接到父元素 */
if (!returnFiber.firstEffect) {
returnFiber.firstEffect = currentFiber.firstEffect;
}
if (currentFiber.lastEffect) {
/* 將當前fiber頭部接到父fiber尾部 */
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = currentFiber.firstEffect;
}
/* 當前尾部作爲最終尾部 */
returnFiber.lastEffect = currentFiber.lastEffect;
}
/* 連接子元素 */
if (currentFiber.effectTag === PLACEMENT) {
/* 當前元素尾部有元素纔會連接 */
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = currentFiber;
} else {
returnFiber.firstEffect = currentFiber;
}
/* 更新尾巴 */
returnFiber.lastEffect = currentFiber;
}
}
}
2.2 commit掛載DOM
function commitWork(currentFiber) {
/* 獲取父元素 */
let returnDom = currentFiber.return.stateNode;
/* 副作用類型 */
if (currentFiber.effectTag === PLACEMENT) {
returnDom.appendChild(currentFiber.stateNode);
}
/* 去除副作用 */
currentFiber.effectTag = null;
}
function commitRoot() {
/* 獲取鏈表頭 */
let currentFiber = wrokProgressRoot.firstEffect;
while (currentFiber) {
commitWork(currentFiber);
currentFiber = currentFiber.nextEffect;
}
wrokProgressRoot = null;
}
總結
ReactDOM的render方法首先將虛擬DOM樹進行擴充,記錄節點的孩子和兄弟,然後將副作用節點按照自底向上的順序記錄在一個鏈表中,commit時實現從裏到外改變DOM。