构建Fiber对象
节点构建顺序:
从最外层节点开始构建,也就是 virtualDOM 树的根节点
构建完成之后接下来就开始构建两个子级节点
注意:只有第一个子级是父级的子级,第二个子级是第一个子级下一个兄弟节点
确定关系之后,再去找第一个子级节点的子节点,还是最左边的去构建这个节点的 Fiber 对象
构建完成之后,再构建该子级的两个子级,然后确定他们之间的关系,确定关系之后发现没了子级,就会去找子节点的同级,按照深度遍历顺序去构建
构建根节点 Fiber 对象
getFirstTask 就是获取任务队列中的第一个排在最前面的小任务,通过第一个小任务对象,构建根节点的 fiber 对象
对于根节点 fiber 对象不需要指定 type 属性,需要指定如下属性:
- props:值为 task.props
- stateNode:存储的是当前节点 DOM 对象
- tag:是一个标记,根节点值为
host_root
- effects:这个数组我们先不获取给一个空数组
- 根节点不需要 effectTag 属性,因此不需要新增、修改、删除,也没有 parennt 因为这是根节点
- child:子级节点属性值,值先指定为 null
const getFirstTask = () => {
/**
* 从任务列队获取任务
*/
const task = taskQueue.pop()
/**
* 返回最外层节点的 fiber 对象
*/
return {
props: task.props,
stateNode: task.dom,
tag: 'host_root',
effects: [],
child: null
}
}
一个根节点 fiber 对象构建完成
拿到 fiber 对象之后且浏览器有空闲时间的时候,就执行 workLoop 方法里面的 while 循环,就会把根节点 fiber 对象传递给方法 executeTask方法,这样我们整个任务调度和 fiber 对象就算运作起来了
构建子级节点 Fiber 对象
子级 fiber 对象的构建在 executeTask 方法中构建完成,executeTask 会调用 reconcileChildren 方法
- 第一个参数为 fiber(父级 fiber 对象)
- 第二个参数为子级 vitrualDOM 对象通过 fiber.props.children 获取
实现 reconcileChildren
reconcileChildren 这个方法第二个参数为 children,有可能是一个对象或数组
- 当我们调用 render 方法的时候 children 是一个对象
- 如果不是我们传的是 createElement 返回的,那么 children 就是一个数组
因为 children 可能是数组或对象,这样的参数传过来特别影响代码之后的操作,所以我们需要定义一个方法判断这个参数是否是数组
// Misc/Arrified/index.js
const arrified = arg => (Array.isArray(arg) ? arg : [arg])
// Reconciliation/index.js
const reconcileChildren = (fiber, children) => {
/**
* children 可能是对象也可能是数组,将 children 转换成数组
*/
const arrifiedChildren = arrified(children)
}
接下来将 arrifiedChildren 数组中 virtualDOM 转换为 Fiber 对象,我们需要一个循环去将数组中的 virtualDOM 构建出一个 fiber 对象
const reconcileChildren = (fiber, children) => {
/**
* children 可能是对象也可能是数组,将 children 转换成数组
*/
const arrifiedChildren = arrified(children)
let index = 0
const numberOfElements = arrifiedChildren.length
let element = null
let newFiber = null
while (index < numberOfElements) {
element = arrifiedChildren[index]
newFiber = {
type: element.type,
props: element.props,
tag: 'host_component',
effects: [],
effectTag: 'placement',
stateNode: null,
parent: fiber
}
fiber.child = newFiber
index++
}
}
设置 stateNode 属性和 tag 属性
这时候就完成了每个 DOM 对象构建成 Fiber 的工作。这里还有点小问题,第一个节点才是父节点,其他节点都是兄弟节点,第二个是第一个兄弟节点。所以第一个子节点才去设置子节点 child 属性,其他的都需要设置兄弟节点
index === 0
是子节点,其他的为彼此兄弟的节点
const reconcileChildren = (fiber, children) => {
const arrifiedChildren = arrified(children)
let index = 0
const numberOfElements = arrifiedChildren.length
let element = null
let newFiber = null
let prevFiber = null
while (index < numberOfElements) {
// 子级 virtualDOM 对象
element = arrifiedChildren[index]
// 子级 fiber 对象
newFiber = {
type: element.type,
props: element.props,
tag: getTag(element),
effects: [],
effectTag: 'placement',
stateNode: null,
parent: fiber
}
// 为 fiber 节点添加 DOM 对象或组件实例对象
newFiber.stateNode = createStateNode(newFiber)
// 为父级 fiber 添加子级 fiber
if (index === 0) {
fiber.child = newFiber
} else {
// 为 fiber 添加下一个兄弟 fiber
prevFiber.sibling = newFiber
}
prevFiber = newFiber
index++
}
}
在处理子节点时,每一个子节点的类型都是不一样的,可以通过一个方法去判定节点的类型,通过不同类型给 tag 设置值
// Misc/GetTag/index.js
const getTag = vdom => {
if (typeof vdom.type === 'string') {
return 'host_component'
}
}
stateNode 属性我们使用 createStateNode 函数调用去获取。需要参数是当前节点 fiber 对象 newFiber。createStateNode 函数里判断 fiber 对象 tag 为 host_component 时,则去生成 DOM 元素
// Misc/CreateStateNode/index.js
import { createDOMElement } from '../../DOM'
const createStateNode = fiber => {
if (fiber.tag === 'host_component') {
return createDOMElement(fiber)
}
}
在 Misc 中创建文件夹为 DOM,专门对于DOM元素的一些操作都在这里
// DOM/createDOMElement.js
import updateNodeElement from "./updateNodeElement"
function createDOMElement(virtualDOM) {
let newElement = null
if (virtualDOM.type === "text") {
// 文本节点
newElement = document.createTextNode(virtualDOM.props.textContent)
} else {
// 元素节点
newElement = document.createElement(virtualDOM.type)
updateNodeElement(newElement, virtualDOM)
}
return newElement
}
构建左侧节点树中剩余节点 Fiber 对象
以上我们已经把根节点,和他的第一层子节点和第一层子节点的同级兄弟节点都已经构建 fiber 对象并且关联了起来,接下来我们要继续往下查找子节点继续构建 fiber 对象
当执行完 reconcileChildren,fiber.child 有值的话,executeTask 返回一个新的 fiber 对象,workLoop 方法里 subTask 重新被赋值继续执行 executeTask。直到 executeTask 子节点为空,结束循环
const executeTask = fiber => {
reconcileChildren(fiber, fiber.props.children)
if (fiber.child) {
return fiber.child
}
console.log(fiber)
}
const workLoop = deadline => {
while(subTask && deadline.timeRemaining() > 1) {
subTask = executeTask(subTask)
}
}
构建剩余子节点的 Fiber 对象
当左侧节点构建完成之后我们定位的应该是最后一个子节点,就根据这个最后一个子节点去查找剩余节点,如果当前节点有同级就去构建该节点。如果没有就退回他的父级,查看他的父级有没有同级,一直退回查找构建 fiber 对象,将所有剩余子节点构建 fiber 对象
const executeTask = fiber => {
reconcileChildren(fiber, fiber.props.children)
if (fiber.child) {
return fiber.child
}
let currentExecuteFiber = fiber
while (currentExecuteFiber.parent) {
// 有同级返回同级
if (currentExecuteFiber.sibling) {
return currentExecuteFiber.sibling
}
// 退到父级
currentExecuteFiber = currentExecuteFiber.parent
}
}