React fiber原理解析及自定義實現(二)

上一節中,介紹了React爲何採用Fiber替換原有的虛擬Dom對比更新方式及一些Fiber基礎概念,本節將模擬實現首次渲染時如何構建Fiber樹及實現節點掛載。segmentfault

實現workLoop

接着上一節,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)
  }
}

構建根節點Fiber對象

聲明getFirstTask方法用於構建根節點Fiber對象:瀏覽器

const getFirstTask = () => {
  // 獲取任務隊列中的任務
  const task = taskQueue.pop()

  // 返回Fiber對象
  return {
    props: task.props,
    stateNode: task.dom,
    // 表明虛擬Dom掛載的節點
    tag: "host_root",
    effects: [],
    child: null
  }
}

構建其他Fiber對象

當構建完根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

相關文章
相關標籤/搜索