实现一个 Mini React:核心功能详解 - fiber 的迷你版实现 (3)

169 阅读4分钟

xdm,又要到饭了,又更新代码了!

总结一下上一篇完成的内容,

  1. 实现了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
        }
        
    }
    
}

先讲解一下我们现在有的代码,

  1. 获取传入fiber的父fiber,接着获取父fiber的真dom (上一篇文章的reconcileChild 函数对于每一个fiber 都创建了对应真dom, 你可以查看一下)
  2. 接着根据当前fiber需要什么类型的操作,调用对应的函数进行处理(Placement - 插入, Update - 更新)
  3. 处理好当前的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做了下面的处理

  1. 如果更新类型 placement, 则代表添加,直接使用 appendChild 就可以
  2. 如果更新类型 update, 则代表更新 (我们在前几篇已经完成了这个函数),比如更新props。
  3. 如果更新类型 deletion, 则代表删除,我们接下来需要创建对应的逻辑
  4. 我们还需要完成 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)。

如果文章对你有帮助,请点个赞支持一下!

啥也不是,散会。