乐趣区

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

在上一节中模仿实现了 Fiber 首次渲染性能,本节将持续模仿实现比照更新性能。

保留上一次 Fiber 对象

如果想要实现比照更新,就须要比对新旧两次 Fiber 对象,找出差别,新的 Fiber 对象能够通过调用 render 办法构建,旧的 Fiber 对象则须要在上一次构建中保留。

在 commitAllWork 办法的结尾:

// ... 其余原有代码
// 备份 Fiber 对象,stateNode 属性此时指向的是以后 Fiber 对象对应的 Dom 元素或者是组件实例对象
fiber.stateNode.__rootFiberContainer = fiber

在 getFirstTask 构建根 fiber 对象的办法中,为最终返回的 fiber 对象增加 alternate 属性,此属性指向上一次构建的 fiber 对象。

return {
    props: task.props,
    stateNode: task.dom,
    tag: "host_root",
    effects: [],
    child: null,
    // 上一次构建的 fiber 对象
    alternate: task.dom.__rootFiberContainer
  }

实现比照

除了根 Fiber 对象之外,其余的 fiber 对象是通过 reconcileChildren 办法构建的,此时须要批改此办法,比照新旧 fiber 对象,为 effectTag 属性赋相应值,如删除为 delete, 更新为 update。


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
    // 旧的 fiber 对象
    let alternate = null
    // 获取父 fiber 对象的子节点 fiber 对象,此对象肯定对应以后 children 数组的第一项
    if (fiber.alternate && fiber.alternate.child) {alternate = fiber.alternate.child}

    while (index < numberOfElements) {element = arrifiedChildren[index]
        // 如果以后子项曾经不存在,然而对应的旧的 fiber 对象存在,则代表删除
        if (!element && alternate) {
            alternate.effectTag = "delete"
            fiber.effects.push(alternate)
        }
        // 代表更新
        else if (element && alternate) {
            newFiber = {
                type: element.type,
                props: element.props,
                tag: getTag(element),
                effects: [],
                effectTag: "update",
                parent: fiber,
                alternate
            }
            if (element.type === alternate.type) {
                // 类型雷同就复用旧 fiber 对象的 stateNode 属性
                newFiber.stateNode = alternate.stateNode
            } else {newFiber.stateNode = createStateNode(newFiber)
            }
        }
        // 代表初始化渲染
        else if (element && !alternate) {
            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++
    }
}

此办法中用到了 getTag 辅助办法:

const getTag = vdom => {if (typeof vdom.type === "string") {return "host_component"} else if (Object.getPrototypeOf(vdom.type) === Component) {return "class_component"} else {return "function_component"}
}

挂载阶段

在 reconcileChildren 办法的处理过程中,通过为 fiber 对象 effectTag 属性赋不同的值,指定了该 fiber 对象是须要插入,删除还是更新,所以在 commitAllWork 办法中须要依据 effectTag 不同的值进行不同的操作:

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)
            }
        }
        else if (item.effectTag === "delete") {item.parent.stateNode.removeChild(item.stateNode)
        } else if (item.effectTag === "update") {if (item.type === item.alternate.type) {
                // 类型雷同更新属性,复用之前模仿 React 虚构 Dom 实现中的代码
                updateNodeElement(item.stateNode, item, item.alternate)
            } else {
                // 类型不同间接替换
                item.parent.stateNode.replaceChild(
                    item.stateNode,
                    item.alternate.stateNode
                )
            }
        }
    })
}

此时通过 render 触发的 fiber 比照更新操作曾经大抵模仿实现实现,前面将通过一个大节模仿实现类组件中通过 setState 实现更新。

类组件 setState

定义 Component 类时,提供 setState 办法:

export class Component {constructor(props) {this.props = props}
  setState(partialState) {scheduleUpdate(this, partialState)
  }
}

setState 办法调用了 scheduleUpdate 办法,并将组件实例和以后更改的 state 传递过来。

在 scheduleUpdate 办法中,向工作队列增加一个工作,并指定闲暇时执行该办法:

const scheduleUpdate = (instance, partialState) => {
  taskQueue.push({
    from: "class_component",
    instance,
    partialState
  })
  requestIdleCallback(performTask)
}

此时,工作指向的根并不是前文中始终应用的容器 Dom 对象,而是组件实例,因而须要批改 getFirstTask 办法:

const getFirstTask = () => {const task = taskQueue.pop()
  // 如果当前任务的 from 指向的是类组件
  if (task.from === "class_component") {const root = getRoot(task.instance)
    task.instance.__fiber.partialState = task.partialState
    return {
      props: root.props,
      stateNode: root.stateNode,
      tag: "host_root",
      effects: [],
      child: null,
      alternate: root
    }
  }

  return {
    props: task.props,
    stateNode: task.dom,
    tag: "host_root",
    effects: [],
    child: null,
    alternate: task.dom.__rootFiberContainer
  }
}
退出移动版