vue3的runtime-core-实现组件props逻辑

157 阅读2分钟

props组件传值应该是很常见的,几乎是每个抽离组件必须使用到的

props

props实现有三个点

1.props可以通过setup这边传入过来

2.使render内的h函数能用this.xxx获取到props的值

3.props是shallowReadonly 类型的只读响应式数据

我们先创建一个新的组件,组件本质就是对象,而且还没实现编译,所以使用app.js的格式创建一个组件就行

Foo.js

export const Foo = {
    render() {
        return h('div', {}, 'foo:' + this.count)
    },
    setup(props) {
        // props 不可更改
        // 使render的h函数内能使用this.count
        // shallowReadonly 类型的响应式对象
        console.log(props)
        props.count++ 
        console.log(props)
    }
}

App.js

之前处理过h传入的第一个参数的类型判断及相关处理,所以直接把Foo传进去就行

import { h } from "../../lib/xin-mini-vue.esm.js"
import { Foo } from './Foo.js'
export const App = {
    // 没搞编译,所以template不写,写render就行
    render() {
        // 返回虚拟VNode
        return h('div',
            {
                id: 'root',
                class: ['bgc-red', 'font-blue'],
            },
            [h('div', {}, 'hi,' + this.msg), h(Foo, {
                count: 99
            })]
        )
    },
    setup() {
        // composition api
        return {
            msg: 'xin-vue'
        }
    }
}

首先就是实现setup传值的需求

component.ts

我们是在setupStatefulComponent函数中调用的setup,所以传值在setupStatefulComponent调用setup时处理

export function createComponentInstance(vnode) {
    const component = {
        vnode,
        type: vnode.type,
        setupState: {},
        props: {} // 加上初始值
    }
    return component
}

// 之前留的TODO不知道大家还记不记得
export function setupComponent(instance) {
    const { vnode: { props } } = instance

    // TODO
    initProps(instance, props)
    // initSlots()

    setupStatefulComponent(instance)

}

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(shallowReadonly(instance.props)) // 这里直接完成第三个需求

        handleSetupResult(instance, setupResult)
    }
}

同时完成第三个需求的时候,在 reactive.ts 中的createActiveObject函数在new Proxy的时候,target不是对象时会报错,我们在里面做一层判断即可

reactive.ts

export function createActiveObject(raw, beseHandlers = mutableHandlers) {
    if (!isObject(raw)) console.warn('target必须是一个对象')
    return new Proxy(raw, beseHandlers)
}

componentProps.ts

我们在开发新功能的时候最好是把所有单独功能抽离出去,看起来不冗余,也更有利于后期维护

这里需要给个空对象,防止 undefined 报错

export function initProps(instance, rawProps) {
    // TODO
    // attr

    // 没有值就给个空对象
    instance.props = rawProps || {}
}

剩下就是一个this指向问题,之前实现 组件代理对象 时实现过,所以套用就行

componentPublicInstance.ts

export const publicInstanceProxyHandlers = {
    get({ _: instance }, key) {
        // setupState
        const { setupState, props } = instance
        if (key in setupState) {
            return Reflect.get(setupState, key)
        }

        if (hasOwn(setupState, key)) {
            return Reflect.get(setupState, key)
        }
        else if (hasOwn(props, key)) {
            return Reflect.get(props, key)
        }

        const publicGetter = Reflect.get(publicPropertiesMap, key)
        if (publicGetter) {
            return publicGetter(instance)
        }
    }
}

这里单独把hasOwn提出去为一个通用工具函数

shared/index.ts

加call同样是this指向问题

export const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key)

结语

this指向问题的实现思路和组件代理对象的思路是一样的,就是看看有没有值,有值就反,没值就加个{}防止报错,下一章就到 实现组件emit功能