Skip to content

1.挂载

先来看一下使用方式

ts
import { h, ref, createApp, render } from '../dist/vue.esm.js'
const Comp = {
    render() {
        return h('div', 'hello world')
    }
}
createApp(Comp).mount('#app')

先处理一下createVNode中的组件类型

ts
// vnode.ts
export function createVNode(type, props?, children = null) {
    let shapeFlag = 0

    if (isString(type)) {
        // div span p h1
        shapeFlag = ShapeFlags.ELEMENT // 1
    } else if (isObject(type)) {
        // type 是 一个对象,表示是一个组件
        // 有状态组件
        shapeFlag = ShapeFlags.STATEFUL_COMPONENT
    }

    // 省略部分代码...

    const vnode = {
        /* 省略部分代码... */
    }

    return vnode
}

组件和元素、文本一样,都存在两种情况,挂载和更新:

ts
const patch = (n1, n2, container, anchor = null) => {
    // 省略部分代码...
    const { shapeFlag, type } = n2

    switch (type) {
        case Text:
        processText(n1, n2, container, anchor)
        break
        default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
            // 处理 dom 元素 div span p h1
            processElement(n1, n2, container, anchor)
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
            // 💡 处理组件的逻辑
            processComponent(n1, n2, container, anchor)
        }
    }
}

创建processComponent函数处理组件的挂载和更新:

ts
const processComponent = (n1, n2, container, anchor) => {
    if (n1 == null) {
        // 挂载
        // 在这里,我们创建一个 mountComponent 函数,来完成挂载
        mountComponent(n2, container, anchor)
    } else {
        // 更新
    }
}

创建mountComponent函数:

ts
const mountComponent = (vnode, container, anchor) => {
    /**
     * 1. 创建组件实例
     * 2. 初始化组件状态
     * 3. 将组件挂载到dom
     */
    // 创建组件实例
    const instance = createComponentInstance(vnode)
    // 初始化组件状态
    setupComponent(instance)
    // 调用render拿到subTree, this应该指向setupState
    const subTree = instance.render.call(instance.setupState)
    // 将subTree挂载到页面
    patch(null, subTree, container, anchor)
}

新建component.ts文件,创建createComponentInstancesetupComponent函数:

ts
import { proxyRefs } from '@vue/reactivity'

/**
 * 创建组件实例
 */
export function createComponentInstance(vnode) {
    const { type } = vnode
    const instance = {
        type,
        vnode,
        // 渲染含糊
        render: null,
        // setup返回的状态
        setupState: null,
        props: {},
        attrs: {},
        // 子树,render的返回值
        subTree: null,
        // 是否已经挂载
        isMounted: false,
    }

    return instance
}

/**
 * 初始化组件
 */
export function setupComponent(instance) {
    const { type } = instance
    const setupResult = proxyRefs(type.setup())
    // 拿到setup返回的状态
    instance.setupState = setupResult
    // 将render函数给instance
    instance.render = type.render
}

2.更新

修改mountComponent方法: 通过ReactiveEffect进行更新

ts
const mountComponent = (vnode, container, anchor) => {
    /**
     * 1. 创建组件实例
     * 2. 初始化组件状态
     * 3. 将组件挂载到dom
     */
    // 创建组件实例
    const instance = createComponentInstance(vnode)
    // 初始化组件状态
    setupComponent(instance)

    const componentUpdateFn = () => {
        /**
         * 区分挂载和更新
         */
        if (!instance.isMounted) {
            // 调用render拿到subTree, this应该指向setupState
            const subTree = instance.render.call(instance.setupState)
            // 将subTree挂载到页面
            patch(null, subTree, container, anchor)
            // 保存子树
            instance.subTree = subTree
            // 表示已经挂载完了
            instance.isMounted = true
        } else {
            const prevSubTree = instance.subTree
            // 调用render拿到subTree, this应该指向setupState
            const subTree = instance.render.call(instance.setupState)
            // 将subTree挂载到页面
            patch(prevSubTree, subTree, container, anchor)
            // 保存这一次的sunTree
            instance.subTree = subTree
        }
    }

    // 创建effect
    const effect = new ReactiveEffect(componentUpdateFn)
    effect.run()
}

3.总结

这样就完成了挂载逻辑,当数据变化的时候,也会触发更新逻辑。注意这个更新processComponent中的更新是不同的,这里是组件的更新,而processComponent中的是组件的挂载和父组件传递的属性变化导致的更新。后面会说到。

Released under the MIT License.