在上一节中,介绍了React为何采用Fiber替换原有的虚拟Dom对比更新方式及一些Fiber基础概念,本节将模拟实现首次渲染时如何构建Fiber树及实现节点挂载。segmentfault
接着上一节,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) } }
声明getFirstTask方法用于构建根节点Fiber对象:浏览器
const getFirstTask = () => { // 获取任务队列中的任务 const task = taskQueue.pop() // 返回Fiber对象 return { props: task.props, stateNode: task.dom, // 表明虚拟Dom挂载的节点 tag: "host_root", effects: [], child: null } }
当构建完根Fiber节点以后,经过while循环构建其他fiber节点,executeTask方法分红两个大的部分,一部分是从上到下遍历VDom树,另外一部分是从下到上遍历VDom树,并为父节点收集全部子节点信息。app
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属性中,这样就构成了节点之间的关系。dom
// 确保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对象的构建。oop
当全部fiber对象遍历构建完成以后,将执行commit挂载操做。此时全局对象pendingCommit存储的是根fiber对象,而根fiber对象的effects属性中存储着fiber树中全部的其余fiber对象,此时只须要遍历effects,而后经过appendChild方法插入到Dom树中便可。code
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首次渲染。下一节将继续模拟实现对比更新功能。component