在上一節中,介紹了React爲何採用Fiber替換原有的虛擬Dom對比更新方式及一些Fiber基礎概念,本節將模擬實現首次渲染時如何構建Fiber樹及實現節點掛載。segmentfault
接着上一節,performTask方法會利用瀏覽器空閒時間不斷的執行workLoop方法,在該方法中,會首先判斷全局對象subTask是否存在,若是不存在就建立根Fiber對象,而後利用while循環構建其他的Fiber對象,當全部Fiber對象構建完成以後,就執行commit操做。數組
// 子任務 let subTask = null // commit操做標誌 let pendingCommit = null const workLoop = deadline => { // 1. 構建根對象 if (!subTask) { subTask = getFirstTask() } // 2. 經過while循環構建其他對象 while (subTask && deadline.timeRemaining() > 1) { subTask = executeTask(subTask) } // 3. 執行commit操做,實現Dom掛載 if (pendingCommit) { commitAllWork(pendingCommit) } }
聲明getFirstTask方法用於構建根節點Fiber對象:瀏覽器
const getFirstTask = () => { // 獲取任務隊列中的任務 const task = taskQueue.pop() // 返回Fiber對象 return { props: task.props, stateNode: task.dom, // 表明虛擬Dom掛載的節點 tag: "host_root", effects: [], child: null } }
當構建完根Fiber節點以後,經過while循環構建其他fiber節點,executeTask方法分紅兩個大的部分,一部分是從上到下遍歷VDom樹,另外一部分是從下到上遍歷VDom樹,併爲父節點收集全部子節點信息。app
const executeTask = fiber => { // 構建子fiber對象 if (fiber.tag === "class_component") { reconcileChildren(fiber, fiber.stateNode.render()) } else if (fiber.tag === "function_component") { reconcileChildren(fiber, fiber.stateNode(fiber.props)) } else { reconcileChildren(fiber, fiber.props.children) } // 若是子級存在 返回子級,將這個子級當作父級 構建這個父級下的子級 if (fiber.child) { // return以後,workLoop方法會將返回值賦值給subTask,而後繼續執行本方法 return fiber.child } // 當執行此段代碼時,說明第一次從上到下的遍歷已經完成,須要從下到上構建剩餘的fiber對象,思路就是判斷是否存在同級,若是存在,則構建同級的子級,若是沒有同級,則查看父級是否存在同級。 let currentExecutelyFiber = fiber while (currentExecutelyFiber.parent) { // 收集全部子節點信息 currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat( currentExecutelyFiber.effects.concat([currentExecutelyFiber]) ) if (currentExecutelyFiber.sibling) { return currentExecutelyFiber.sibling } currentExecutelyFiber = currentExecutelyFiber.parent } pendingCommit = currentExecutelyFiber }
在excuteTask方法中會調用reconcileChildren方法建立子fiber對象,在children數組中分爲兩類:第一個和其他,第一個將添加到父節點的child屬性上,其他的將添加到前一個子節點的sibling屬性中,這樣就構成了節點之間的關係。dom
// 確保children是一個數組 const arrified = arg => (Array.isArray(arg) ? arg : [arg]) const reconcileChildren = (fiber, children) => { // 將children 轉換成數組 const arrifiedChildren = arrified(children) let index = 0 let numberOfElements = arrifiedChildren.length // 循環過程當中的循環項 就是子節點的 virtualDOM 對象 let element = null // 子級 fiber 對象 let newFiber = null // 上一個兄弟 fiber 對象 let prevFiber = null while (index < numberOfElements) { element = arrifiedChildren[index] newFiber = { type: element.type, props: element.props, tag: getTag(element), effects: [], effectTag: "placement", parent: fiber } // 爲fiber節點添加DOM對象或組件實例對象 newFiber.stateNode = createStateNode(newFiber) // 構建關係 if (index === 0) { fiber.child = newFiber } else if (element) { prevFiber.sibling = newFiber } // 更新上一個fiber對象 prevFiber = newFiber index++ } }
createStateNode方法用於爲fiber對象構建createStateNode屬性,fiber對象能夠大體分爲兩類:原生的Dom元素和自定義類或者函數。函數
const createReactInstance = fiber => { let instance = null if (fiber.tag === "class_component") { // 建立實例 instance = new fiber.type(fiber.props) } else { // 函數沒有實例,直接返回函數自己 instance = fiber.type } return instance } const createStateNode = fiber => { if (fiber.tag === "host_component") { // 此處複用了實現React虛擬Dom中的源碼 return createDOMElement(fiber) } else { return createReactInstance(fiber) } }
經過上述代碼,完成除根節點以外其他fiber對象的構建。oop
當全部fiber對象遍歷構建完成以後,將執行commit掛載操做。此時全局對象pendingCommit存儲的是根fiber對象,而根fiber對象的effects屬性中存儲着fiber樹中全部的其餘fiber對象,此時只須要遍歷effects,而後經過appendChild方法插入到Dom樹中便可。code
const commitAllWork = fiber => { // 循環 effets 數組 構建 DOM 節點樹 fiber.effects.forEach(item => { if (item.tag === "class_component") { item.stateNode.__fiber = item } if (item.effectTag === "placement") { let fiber = item let parentFiber = item.parent // 找到普通節點父級 排除組件父級,由於組件父級是不能直接追加真實DOM節點的 while ( parentFiber.tag === "class_component" || parentFiber.tag === "function_component" ) { parentFiber = parentFiber.parent } // 若是子節點是普通節點 找到父級 將子節點追加到父級中 if (fiber.tag === "host_component") { parentFiber.stateNode.appendChild(fiber.stateNode) } } }) }
此時,完成模擬實現Fiber首次渲染。下一節將繼續模擬實現對比更新功能。component