As of June 2023: The usage of defineEmits with the setup script renders this answer outdated, but it's still possible to extend Vue in order to incorporate types on the setup context object. It's important to note that this method is only compatible with versions preceding 3.2.46:
One can avoid installing vue-typed-emit and instead opt for this approach: Begin by defining an interface specifying the structure your events should adhere to, where the event key is 'event' and the type represents the emitted event's type 'args'.
interface Events {
foo?: string;
bar: number;
baz: { a: string, b: number };
}
You can then import and utilize the existing SetupContext interface from Vue, extending it to impose additional constraints on the emit functions parameters.
interface SetupContextExtended<Event extends Record<string, any>> extends SetupContext {
emit: <Key extends keyof Event>(event: Key, payload: Event[Key]) => void;
}
This interface essentially supersedes the current
emit(event: string, args: any) => void
with an emit function that receives 'event' as a key of the 'Events' interface and its corresponding type as 'args'.
Subsequently, you can define the setup function within the component, substituting SetupContext with SetupContextExtended while supplying the 'Events' interface.
setup(props, context: SetupContextExtended<Events>) {
context.emit('foo', 1); // TypeError - 1 should be string
context.emit('update', 'hello'); // TypeError - 'update' does not exist on type Events
context.emit('foo', undefined); // Success
context.emit('baz', { a: '', b: 0 }); // Success
}
Example of a functional component:
<script lang="ts">
import { defineComponent, SetupContext } from 'vue';
interface Events {
foo?: string;
bar: number;
baz: { a: string, b: number };
}
interface SetupContextExtended<Event extends Record<string, any>> extends SetupContext {
emit: <Key extends keyof Event>(event: Key, payload: Event[Key]) => void;
}
export default defineComponent({
name: 'MyComponent',
setup(props, context: SetupContextExtended<Events>) {
context.emit('foo', 1); // TypeError - 1 should be string
context.emit('update', 'hello'); // TypeError - 'update' does not exist on type Events
context.emit('foo', undefined); // Success
context.emit('baz', { a: '', b: 0 }); // Success
}
});
</script>
To make this extended type available across all new and existing components - You have the option to enhance the Vue module itself to include the custom SetupContextExtended interface in your current imports. In this illustration, it's included in shims-vue.d.ts, however, feel free to place it in a separate file if desired.
// shims-vue.d.ts
import * as vue from 'vue';
// Pre-existing content
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare module 'vue' {
export interface SetupContextExtended<Event extends Record<string, any>> extends vue.SetupContext {
emit: <Key extends keyof Event>(event: Key, payload: Event[Key]) => void;
}
}
Finalized component with enhanced Vue module:
<script lang="ts">
import { defineComponent, SetupContextExtended } from 'vue';
interface Events {
foo?: string;
bar: number;
baz: { a: string, b: number };
}
export default defineComponent({
name: 'MyComponent',
setup(props, context: SetupContextExtended<Events>) {
context.emit('baz', { a: '', b: 0 }); // Success
}
});
</script>
Personally, I prefer to define and export the Events interface in the parent component and then import it into the child component so that the parent dictates the contract governing the emit events of the child.