在上一节中模拟实现了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
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。