xdm,又要到饭了,又更新代码了!
总结一下上一篇完成的内容,
- 实现了mini fiber 的第2版,实现了 fiber tree 从 0 - 1 构建
有兴趣的可以点这里查看fiber 的迷你版实现 (2)
这篇将会实现fiber树更新真 dom 的逻辑实现,
fiber树可以简单的理解成组件树的一种表现形式,每一个fiber包含了渲染真dom的所有需要的信息,
Fiber 节点定义
class FiberNode {
constructor(type, props, parent = null, sibling = null, child = null) {
this.type = type; // 组件类型
this.props = props; // 组件属性
this.parent = parent; // 父节点
this.sibling = sibling; // 兄弟节点
this.child = child; // 子节点
this.effectTag = null; // 标记需要执行的操作(如更新)
this.state = null; // 组件的状态(useState)
this.hooks = []; // 组件的 Hook 列表
this.currentHook = 0; // 当前 Hook 的索引
}
}
那么现在我们来实现一下根据fiber的数据结构渲染真dom 的逻辑,
function commitWork(fiber) {
let currentFiber = fiber
while(currentFiber!==null) {
if (currentFiber.type && typeof currentFiber.type === 'string') {
const parentDom = getParentDom(currentFiber.parent)
if (!parentDom) { console.error('无法找到 DOM 父节点'); return; }
switch(currentFiber.effectTag) {
case 'PLACEMENT':
if (currentFiber.stateNode) {
parentDom.appendChild(currentFiber.stateNode)
}
break;
case 'UPDATE':
if (currentFiber.stateNode) {
updateDom(currentFiber.stateNode, currentFiber.alternate.props, currentFiber.props)
}
break;
case 'DELETION':
commitDeletion(currentFiber, parentDom);
break;
default:
break;
}
}
if (currentFiber.child) {
currentFiber = currentFiber.child
continue
}
while(currentFiber !== null) {
if (currentFiber.sibling) {
currentFiber = currentFiber.sibling
break;
}
currentFiber = currentFiber.parent
}
}
}
先讲解一下我们现在有的代码,
- 获取传入fiber的父fiber,接着获取父fiber的真dom (上一篇文章的reconcileChild 函数对于每一个fiber 都创建了对应真dom, 你可以查看一下)
- 接着根据当前fiber需要什么类型的操作,调用对应的函数进行处理(Placement - 插入, Update - 更新)
- 处理好当前的fiber, 尝试返回当前fiber的子fiber视作下一个需要处理的fiber,如果当前的fiber 没有对应的子fiber,则尝试寻找当前fiber的兄弟fiber视作下一个需要处理的fiber,如果当前fiber没有对应的兄弟fiber,则回溯当前fiber的父fiber接着进行查找父fiber的兄弟fiber视作下一个处理的fiber。如果还是无法找到,则fiber 的遍历结束,这代表根据当前传入fiber和与之关联的所有fiber都完成了真dom的更新。
上面的3点就是处理的精华,请理解一下,理解透彻了,就表明你对fiber的构建和如何遍历有了清晰的了解,其本质就是根据fiber的sibling 以及 parent, child 可以完成整个链路的更新,而单独的fiber 不需要依赖fiber树也可以完成更新的原理也在这里了。
这也回答了面试常问的,fiber 有3个指针,它们的作用?以及为什么不能只使用一个指针?
上面的函数对于 fiber渲染真dom做了下面的处理
- 如果更新类型 placement, 则代表添加,直接使用 appendChild 就可以
- 如果更新类型 update, 则代表更新 (我们在前几篇已经完成了这个函数),比如更新props。
- 如果更新类型 deletion, 则代表删除,我们接下来需要创建对应的逻辑
- 我们还需要完成 getParentDom 函数的逻辑
function getParentDom(fiber) {
let parentFiber = fiber
while(parentFiber) {
if (parentFiber.stateNode && typeof parentFiber.type === 'string')
return parentFiber.stateNode
parentFiber = parentFiber.parent
}
return null
}
function commitDeletion(fiber, parentDom) {
if (fiber.type && fiber.type === 'string') {
parentDom.removeChild(fiber.stateNode)
} else if (fiber.child) {
commitDeletion(fiber.child, parentDom)
}
}
这里已经完成了从fiber 渲染真dom的逻辑。这里还有个问题,就是什么时机进行这一步操作。
其实,阅读了上一篇创建fiber树,就会知道,渲染真dom 需要等一次fiber树的处理,即完成了一次fiber树更新之后才开始渲染真dom。
那么我们现在添加完成的commitWork函数在上一节完成的workLoop 函数,
// 提交阶段
function commitRoot() {
commitWork(currentRoot.child)
currentRoot = currentRoot.alternate;
}
function workLoop(deadline) {
while (nextUnitOfWork && deadline.timeRemaining() > 0)
{
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
if (nextUnitOfWork) {
requestHostCallback(workLoop)
}
else {
commitRoot()
}
workLoopScheduled = false
}
这一章节讲解了从0构建fiber树渲染真dom的创建的过程,包括添加/更新/删除等操作的逻辑,以及什么时机进行一次渲染。
下一篇将完成fiber如何管理hooks以及hooks的更新,以及配合fiber 结构更新之前的useState hook,最后从源码角度讲解useState的运行原理。
如果这样的长度/强度你觉得可以接受,觉得有帮助,可以继续阅读下一篇,实现一个 Mini React:核心功能详解 - fiber 的迷你版实现(4)。
如果文章对你有帮助,请点个赞支持一下!
啥也不是,散会。