在上一节中,介绍了React为什么采纳Fiber替换原有的虚构Dom比照更新形式及一些Fiber根底概念,本节将模仿实现首次渲染时如何构建Fiber树及实现节点挂载。

实现workLoop

接着上一节,performTask办法会利用浏览器闲暇工夫一直的执行workLoop办法,在该办法中,会首先判断全局对象subTask是否存在,如果不存在就创立根Fiber对象,而后利用while循环构建其余的Fiber对象,当所有Fiber对象构建实现之后,就执行commit操作。

// 子工作let subTask = null// commit操作标记let pendingCommit = nullconst 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树,并为父节点收集所有子节点信息。

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属性中,这样就形成了节点之间的关系。

// 确保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对象的构建。

节点挂载

当所有fiber对象遍历构建实现之后,将执行commit挂载操作。此时全局对象pendingCommit存储的是根fiber对象,而根fiber对象的effects属性中存储着fiber树中所有的其余fiber对象,此时只须要遍历effects,而后通过appendChild办法插入到Dom树中即可。

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首次渲染。下一节将持续模仿实现比照更新性能。