第二十一章 vue3实现组件代理对象

440 阅读3分钟

vue3实现组件代理对象

首先我们在render函数中使用this.msg访问setup中return出来的对象的值的时,会发现访问到的值都是undefined。 所以我们需要在render中通过this访问到setup函数中返回出来的对象的值!

代理对象初步构建

1 要实现这个,第一点我们就应该找到render函数调用的位置,并且改变render函数的this指向(如下图所示,render函数的调用位置就在setupRenderEffect函数中)

image.png

2 接着我们就要思考改变的this指向的对象应该是谁,这时候我们就想着需要一个代理对象来替我们做这件事情。所以就有了在component实例中去挂载上一个代理对象,然后对代理对象proxy构造函数中的get做一个代理访问的操作(当我访问对象中的值的时候,我们就会到setupState中去看看是不是setupState对象中的属性,如果是就放回setupState中属性的值出来)

注:setupState其实就是我们前面对setup函数放回出来对象结果的一个存储;

如下图:

image.png

3 最后我们就在步骤一中的render函数调用处使用call去改变this指向,指向代理对象

image.png

$el的实现

有了上面的代理对象的初步构建,$el的实现也就不太难了

1 首先我们也是在代理对象中判断,如果key==="$el"时就返回实例虚拟节点上的el就行了,然后我们发现其实我们实例虚拟节点上的el并没有挂载或者处理,所以我们下面就需要对这个对象的el进行一个处理

image.png

2 然后我们需要找到创建节点的位置也就是mountElement函数中,并且将创建好的节点挂载到虚拟节点的el上;当redner函数调用完毕放回的subTree中就可以拿到前面mountElement函数中保存的vnode中的el,当patch执行完毕后,所有的element都mount完毕,我们就把subTree.el赋值给vnode.el就可以了。

image.png

3 为了验证我们的el确实挂载成功了,我们需要在app.js中去给指定self变量,然后在render中给self全局属性赋this对象,在浏览器控制台中通过self去查看el是否挂载成功,实现如下:

image.png

结果如图:

image.png

使用vue3中我们可以知道,其实我们还有其他全局变量如 optionsoptions data $props...,如图:

image.png

因此我们就考虑把代理的proxy中的handlers抽离出去

image.png

4 抽离出来的PublicInstanceProxyHandlers中的我们可以通过map去代替掉用if来判断key的代码,让代码更简洁高效

image.png

5 最后我们还可以优化一个,就是render中的mountComponent的那几个初始化节点的命名,可以将vnode改成initialVNode,更加具有语义化

image.png

修改的文件及代码:

App.js

import {h} from '../../lib/guide-ljp-vue.esm.js'
window.self = null
export const App = {
    // .vue
    // <template></template> 最总转化成render函数
    // render 由于还没有实现编译功能 所以先直接写render函数
    render() {
        window.self = this
        // ui
        return h('div',
        {
            id:'root',
            class:['red','hard']
        },
        // 在render中通过this访问到setup函数中返回出来的对象的值
        'hi,  '+this.msg
        // [h('p',{class:'red'},'hi'),h('p',{class:'blue'},'mini-vue')]
        )
    },

    setup(){

        return{
            msg:'mini-vueljp'
        }
    }
}

components.ts

import { PublicInstanceProxyHandlers } from "./componentPublicInstance"

export function createComponentInstance(vnode) {
    const component = {
        vnode,
        type:vnode.type,
        setupState:{}
    }
    return component
}

export function setupComponent(instance) {
    // initProps
    // initSlots

    setupStatefulComponent(instance)
}

function setupStatefulComponent(instance){
    const Component = instance.type

    instance.proxy = new Proxy(
        {_:instance},
        PublicInstanceProxyHandlers
    )

    const {setup} = Component

    if(setup){
       const setupResult = setup()

       handleSetupResult(instance,setupResult)
    }
}

function handleSetupResult(instance,setupResult){
    // function Object

    if(typeof setupResult === 'object'){
        instance.setupState = setupResult
    }

    finishComponentSetup(instance)
}

function finishComponentSetup(instance){
    const Component = instance.type
    if(Component.render){
        instance.render = Component.render
    }
}

render.ts

import { isObject } from "../shared/index"
import { createComponentInstance, setupComponent } from "./components"

export function render(vnode,container){
    // patch

    patch(vnode,container)
}

function patch(vnode,container){
    // debugger
    if(isObject(vnode.type)){
        // 处理组件
        processComponent(vnode,container)
    } else {
        processElement(vnode,container)
    }

}

function processComponent(vnode,container){

    mountComponent(vnode,container)
}

function processElement(vnode,container) {
    mountElement(vnode,container)
}

function mountElement(vnode,container){
    // 保存根节点到vnode上
    const el = ( vnode.el = document.createElement(vnode.type))

    const {children,props} = vnode

    // children
    if(typeof children === 'string') {
        el.textContent = children
    } else if(Array.isArray(children)) {
       mountChildren(children,el)
    }

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

    container.append(el)
}

function mountChildren(children,el) {
    children.forEach(v => {
        patch(v,el)
    })
}

function mountComponent(initialVNode,container) {
    const instance = createComponentInstance(initialVNode)

    setupComponent(instance)
    setupRenderEffect(instance,initialVNode,container)
}

function setupRenderEffect(instance,initialVNode,container) {
    // render函数调用的地方 call改变this指向代理的对象
    const {proxy} = instance
    const subTree = instance.render.call(proxy)

    patch(subTree,container)

    // element 所有都mount完毕
    initialVNode.el = subTree.el
}

vnode.ts

export function createVNode(type,props?,children?){
    const vnode = {
        type,
        props,
        children,
        el: null
    }
    return vnode
}

componentPublicInstance.ts

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

export const PublicInstanceProxyHandlers = {
    get({_:instance},key){
        // setupState
        const {setupState} = instance
        if(key in setupState){
            return setupState[key]
        }

        // if(key === '$el'){
        //     return instance.vnode.el
        // }
        const publicGetter = publicPropertiesMap[key]
        if(publicGetter){
            return publicGetter(instance)
        }

        // 使用vue3中我们可以知道,其实我们还有其他全局变量如 $options $data $props...
    }
}