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

10次阅读

共计 3623 个字符,预计需要花费 10 分钟才能阅读完成。

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

正文完
 0