vue3的runtime-core-实现组件 emit 功能

196 阅读2分钟

emit 大家应该不陌生了,在很多时候父子组件需要进行事件传递的时候使用到,emit的字符串处理可能会繁琐一些

emit

首先就是写好父子组件的代码

App.js

export const App = {
    name: 'App',
    render() {
        // emit
        return h('div', {}, [h('div', {}, 'App'), h(Foo, {
            // onAdd(a, b) {
            //     console.log('onAdd', a, b);
            // },
            onAddFooFoo(a, b) {
                console.log('onAddFooFoo', a, b)
            }
        })])
    },
    setup() {

        return {}
    }
}

Foo.js

emit是setup第二个参数对象里的一个属性

export const Foo = {
    render() {
        const btn = h('button', {
            onClick: this.emitAdd
        }, 'emitAdd')

        const foo = h('p', {}, 'foo')
        return h('div', {}, [foo, btn])
    },
    setup(props, { emit }) {
        const emitAdd = () => {
            console.log('emitAdd')
            // emit('add', 1, 2)
            emit('add-foo-foo', 1, 2)
        }
        return {
            emitAdd
        }
    }
}

首先解决setup传值问题,还是和props的处理方式一致

component.ts

import { emit } from "./componentEmit"
export function createComponentInstance(vnode) {
    const component = {
        vnode,
        type: vnode.type,
        setupState: {},
        props: {},
        emit: (event) => { } // 加上预设值
    }
    component.emit = emit.bind(null, component)
    // 这里是一个处理方法,让用户使用emit时不需要传入instance
    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(shallowReadonly(instance.props), {
            emit: instance.emit // 这里就直接把emit放到对象里传过去
        })

        handleSetupResult(instance, setupResult)
    }
}

接下来就实现给component.emit = emit的componentEmit.ts文件了

componentEmit.ts

import { camelize, toHandlerKey } from "../shared/index";

export function emit(instance, event, ...args) { // ...args 是为了接受多项传值
    console.log('emit' + event);
    const { props } = instance //那边bind的传值
    const emitHandlerName = toHandlerKey(camelize(event))
    const emitHandler = Reflect.get(props, emitHandlerName)
    emitHandler && emitHandler(...args) // 展开多项传值,并传过去
}

shared.ts

camelize函数是为了实现xx-xx-xx这种串烧式写法,替换为驼峰写法,多重串烧也可以处理

capitalize函数是为了处理首字母大写

toHandlerKey函数是为了实现onXxx这种写法,并做了处理

export const camelize = (str: string) => str.replace(/-(\w)/g, (_, c: string) => c ? c.toUpperCase() : '')

// 等同于 const camelize = (str: string) => {
//                  return str.replace(/-(\w)/g, (_, c: string) => {
//                        return c ? c.toUpperCase() : ''
//             })
//        }


export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1)

export const toHandlerKey = (str: string) => str ? `on${capitalize(str)}` : ''

结语

emit的实现式很简单的,主要是对字符串的处理是比较多的,不适应串烧写法会更简单,当然我这里也不是一比一完全还原了vue3的emit写法,只是核心。好好学习,天天向上!!