When building Vue 3 components, especially with the <script setup> syntax, you'll often need to emit custom events. Vue provides two main ways to do this: using $emit or defineEmits. While both accomplish the same goal, they serve different use cases and offer varying levels of type safety and clarity.

$emit

Traditionally, $emit has been used in the Options API and is also available in the Composition API through the setup context:

export default {
  setup(props, { emit }) {
    emit('custom-event', 'hello')
  }
}

In <script setup>, $emit can be used directly in templates, but it lacks type checking and auto-completion:

<script setup>
function handleClick() {
  // Not type-safe
  $emit('custom-event', 'hello')
}
</script>

defineEmits

Vue 3 introduces defineEmits for <script setup>, which offers better developer ergonomics and full TypeScript support:

const emit = defineEmits<{
  (e: 'custom-event', value: string): void
  (e: 'another-event', id: number): void
}>()

emit('custom-event', 'hello')
emit('another-event', 123)

You can also use it with a simple array if you're not using TypeScript:

const emit = defineEmits(['custom-event', 'another-event'])

Key differences

Feature $emit defineEmits
Availability Options API or global in template <script setup> only
Type safety No Yes
Auto-completion No Yes (with TypeScript)
Recommended usage Legacy or quick use Preferred in <script setup>
Explicit event list No Yes

Conclusion

If you're using the <script setup> syntax, defineEmits is the modern and type-safe way to handle custom events. It makes your components more maintainable and your development experience smoother. Use $emit only when working within the Options API or when you don't need strong typing.