在上一节中模仿实现了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 }}