Component Emits
Developer Interface
Continuing from props, let's implement emits.
The implementation of emits is relatively simple, so it will be finished quickly.
In terms of the developer interface, emits will be received from the second argument of the setup function.
const MyComponent: Component = {
props: { someMessage: { type: String } },
setup(props: any, { emit }: any) {
return () =>
h('div', {}, [
h('p', {}, [`someMessage: ${props.someMessage}`]),
h('button', { onClick: () => emit('click:change-message') }, [
'change message',
]),
])
},
}
const app = createApp({
setup() {
const state = reactive({ message: 'hello' })
const changeMessage = () => {
state.message += '!'
}
return () =>
h('div', { id: 'my-app' }, [
h(
MyComponent,
{
'some-message': state.message,
'onClick:change-message': changeMessage,
},
[],
),
])
},
})
Implementation
Similar to props, let's create a file called ~/packages/runtime-core/componentEmits.ts
and implement it there.~/packages/runtime-core/componentEmits.ts
export function emit(
instance: ComponentInternalInstance,
event: string,
...rawArgs: any[]
) {
const props = instance.vnode.props || {}
let args = rawArgs
let handler =
props[toHandlerKey(event)] || props[toHandlerKey(camelize(event))]
if (handler) handler(...args)
}
~/packages/shared/general.ts
export const capitalize = (str: string) =>
str.charAt(0).toUpperCase() + str.slice(1)
export const toHandlerKey = (str: string) => (str ? `on${capitalize(str)}` : ``)
~/packages/runtime-core/component.ts
export interface ComponentInternalInstance {
// .
// .
// .
emit: (event: string, ...args: any[]) => void
}
export function createComponentInstance(
vnode: VNode,
): ComponentInternalInstance {
const type = vnode.type as Component
const instance: ComponentInternalInstance = {
// .
// .
// .
emit: null!, // to be set immediately
}
instance.emit = emit.bind(null, instance)
return instance
}
You can pass this to the setup function.
~/packages/runtime-core/componentOptions.ts
export type ComponentOptions = {
props?: Record<string, any>
setup?: (
props: Record<string, any>,
ctx: { emit: (event: string, ...args: any[]) => void },
) => Function // To receive ctx.emit
render?: Function
}
const mountComponent = (initialVNode: VNode, container: RendererElement) => {
const instance: ComponentInternalInstance = (initialVNode.component =
createComponentInstance(initialVNode));
const { props } = instance.vnode;
initProps(instance, props);
const component = initialVNode.type as Component;
if (component.setup) {
// Pass emit
instance.render = component.setup(instance.props, {
emit: instance.emit,
}) as InternalRenderFunction;
}
Let's test the functionality with an example of the developer interface we assumed earlier!
If it works properly, you can now communicate between components using props/emit!
Source code up to this point:
chibivue (GitHub)