实现一个精简React -- 函数组件的实现原理与渲染机制(4)

140 阅读2分钟

函数组件在React中是一个非常重要的概念,他可以帮助我们精简代码结构,根据功能拆分不同的组件并加以复用。
但上一篇的逻辑中,我们只对普通标签和文本做了处理,并没有函数组件相关的逻辑。在这一篇的文章中我们要分析函数组件并实现他的渲染。

针对函数组件生成虚拟dom的处理

通过以下示例可以看出,函数组件本质上是一个纯函数,其核心特征是通过执行函数来生成对应的DOM结构,因此,在生成dom的相关逻辑中,可以针对此特性进行判断。

const Counter = ({ num }) => {
    return (
        <div>Counter: {num}</div>
    )
}
// 执行 Counter 后 可以得到里面dom结构

函数组件相当于是一个盒子,执行函数组件就是开盒的过程,开盒完成后就可以获取到对应的dom,但是函数组件会留在dom树中,所以要考虑到函数组件没有真实dom,并不能挂载的情况。

image.png

相关逻辑优化

在虚拟DOM的创建过程中,考虑到函数组件的传参,所以在执行函数组件的时候要将props传进去。
因此在 createElement 函数中做一下兼容。

const createElement = (type, props, ...children) => {
    return {
        type,
        props: {
            ...props,
            children: children.map(child => {
                // 针对传参进行兼容,扩展基础类型支持
                const isPrimitive = ['string', 'number'].includes(typeof child)
                return isPrimitive ? createTextEl(child) : child
            })
        }
    }
}

同时在上一篇创建链表 perfromFiberOfUnit 的逻辑中也要针对函数组件进行处理。

function performFiberOfUnit(fiber) {
    // 判断当前dom是不是函数组件,不是才执行创建dom方法
    const isFunctionComponent = typeof fiber.type === 'function'
    
    // 仅处理原生DOM组件
    if(!isFunctionComponrnt){ 
        if(!fiber.dom){ 
            // 1.创建dom节点 
            // ... 
            // 2.设置props 
            // ... 
        }
    }

    // 因为执行函数组件将直接得到dom,所以需要手动包裹成数组,并将对应的props传进去 
    const children = isFunctionComponrnt 
                    ? [fiber.type(fiber.props)] 
                    : fiber.props.children 
    
    // 转化成链表,做好指针关系 
    // ...
}

考虑到函数组件执行后其中可能没有dom结构的情况

function commitWork(work){
    // ...

    // 兼容function component的情况。
    // 因为在function component的dom节点是null,所以要循环往上查找真实的dom并挂载
    while(!parentWork.dom){
        parentWork = parentWork.parent
    }

    // ...
}

项目源码:github.com/Cuimc/mini-…