Fiber學習

生成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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章