June 2023 Edit: Using defineEmits with setup script makes this answer obsolete but you can also extend vue to get types on the setup context object. As pointed out however, this will only work with versions prior to 3.2.46:
Installing vue-typed-emit is unnessesary and can be replaced by using this method: Firstly you can define the interface in which you want your events to conform to where the event key is 'event' and the type is the event's emitted type 'args'.
interface Events {
foo?: string;
bar: number;
baz: { a: string, b: number };
}
You can then import and make use of the existing SetupContext interface from vue and define an extension of this with added restrictions 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 replaces the existing emit(event: string, args: any) => void
with an emit function that accepts 'event' as a key of the 'Events' interface and its corresponding type as 'args'.
We can now define our setup function in the component, replacing SetupContext with SetupContextExtended and passing in 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
}
Working 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>
Now to make this extended type available in all existing and future components - You can then augment the vue module itself to include this custom SetupContextExtended interface in your existing imports. For this example its added into shims-vue.d.ts but you should be able to add it to a dedicated file if that is desired.
// shims-vue.d.ts
import * as vue from 'vue';
// Existing stuff
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;
}
}
Final component with augmented 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>
Using this I personally define and export the Events interface in the parent component and import it into the child so the parent defines the contract governing the child's emit events