vue3的runtime-core-实现组件代理对象

109 阅读1分钟

在我们开发时使用this.$xx的时候实际上h函数this指向不是天然性指向节点初始化后的this,所以我们这章就实现this.$xx,顺便封装下映射关系,可以让后续的this.$xx实现起来更简单没可读性也更高

h函数this指向及$xx问题

我们在h中使用this时,拿到的是setup return出去的值

这里我是直接抽离出去了,省的看到后面还要重新捋一遍

component.ts

export function createComponentInstance(vnode) {
    const component = {
        vnode,
        type: vnode.type,
        setupState: {} // 在这里声明好setupState,以便后续使用
    }
    return component
}


function setupStatefulComponent(instance: any) {
    const Component = instance.type
    
    // 就在这里初始化一下,用代理的方式完成
    instance.proxy = new Proxy({_: instance}, publicInstanceProxyHandlers)

    const { setup } = Component
    if (setup) {
        // setup可以返回一个function,也可以返回一个object
        // 返回function我们认为是它是组件的一个render函数
        // 返回object认为是一个会把object注入到当前的组件的上下文中

        const setupResult = setup()

        handleSetupResult(instance, setupResult)
    }
}

componentPublicInstance.ts

这个是抽离出去的文件

这里用的就是映射关系实现的,后续有别的$xx需要实现只需要在map里加上相应的代码即可

const publicPropertiesMap = {
    $el: (i) => i.vnode.el
}


export const publicInstanceProxyHandlers = {
    get({ _: instance }, key) {
        // setupState
        // 实现this指向问题
        const { setupState } = instance
        if (key in setupState) {
            return Reflect.get(setupState, key)
        }
        // 实现$xx
        const publicGetter = Reflect.get(publicPropertiesMap, key)
        if (publicGetter) {
            return publicGetter(instance)
        }
    }
}

vnode.ts

在vnode里提前声明变量

export function createVNode(type, props?, children?) {
    const vnode = {
        type,
        props,
        children,
        el: null // 实现$el用的
    }
    return vnode

因为$el是真实dom,所以需要在初始化完成之后的时候赋值一下,以保证拿到的是对的

所以我们需要在patch结束后进行赋值

render.ts

function mountElement(vnode: any, container: any) {
    const { type, props, children } = vnode
    const el = (vnode.el = document.createElement(type))

    // string array<TODO>
    if (typeof children === 'string') {
        el.textContent = children
    }
    else if (Array.isArray(children)) {
        mountChildren(vnode, el)
    }


    for (const key in props) {
        const value = props[key]
        el.setAttribute(key, value)
    }

    container.append(el)
}


function setupRenderEffect(instance: any, initialVNode: any, container: any) {
    const { proxy } = instance
    // subTree是一个VNode tree
    const subTree = instance.render.call(proxy)

    // vnode -> patch
    // vnode -> element -> mountElement
    patch(subTree, container)

    // element -> mount
    initialVNode.el = subTree.el
}

使用

这里直接在控制台调试输出看看有没有值就行

window.self = null // 全局绑定
export const App = {
    // 没搞编译,所以template不写,写render就行
    // 这里假设用户写了render
    render() {
        window.self = this // 赋值this
        // 返回虚拟VNode
        return h('div',
            {
                id: 'root',
                class: ['bgc-red', 'font-blue']
            },
            `hi,${this.msg}`
            // this.$el -> get root element
            // string
            // 'h1,xin-vue'
            // array
            // [h('p', { class: 'font-green' }, 'hi'), h('p', {class: 'font-pink'}, 'xin-vue')]
        ) // 没实现this问题
    },
    setup() {
        // composition api
        return {
            msg: 'xin-vue'
        }
    }
}

结语

实现起来很简单,分辨清楚赋值的是元素还是组件以及赋值时机的问题就很容易实现,后续如果需要添加别的$xx也只需要在map里添加相关代码就行,避免造成代码冗余和美观度