乐趣区

关于javascript:React-fiber原理解析及自定义实现二

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

实现 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 树,并为父节点收集所有子节点信息。

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

退出移动版