Leveraging defineExpose() in Vue 3: Empowering Component Communication

Louis Young
5 min readJul 4, 2023

--

Vue 3 is a awesome framework for building cool and modern web apps. One of the new features that Vue 3 brings to the table is the defineExpose() function, which lets you expose some stuff from your component to its parent.

In this post, I will tell you what defineExpose() does, why you should use it, and how to use it in your Vue 3 projects.

Photo by Nick Fewings on Unsplash

What is defineExpose()?

defineExpose() is a function that you can call inside the setup() function of your component. It takes an object as an argument, where the keys are the names of the stuff you want to expose, and the values are the actual stuff.

For example, suppose you have a component called Counter that has a data thingy called count and a method thingy called increment. You can use defineExpose() to expose them to the parent component like this:

<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">+1</button>
</div>
</template>

<script>
import { ref, defineExpose } from 'vue'
export default {
setup() {
// define data thingy
const count = ref(0)
// define method thingy
const increment = () => {
count.value++
}
// expose data and method thingy
defineExpose({
count,
increment
})
// return nothing
return {}
}
}
</script>

By using defineExpose(), you are telling Vue that the parent component can access the count and increment stuff of the Counter component. This means that you can use them in the parent template or script like this:

<template>
<div>
<h1>Counter App</h1>
<Counter ref="counter" />
<button @click="reset">Reset</button>
</div>
</template>

<script>
import { ref } from 'vue'
import Counter from './Counter.vue'
export default {
components: {
Counter
},
setup() {
// get a reference to the Counter component
const counter = ref(null)
// define a method to reset the count
const reset = () => {
counter.value.count = 0
}
// return the method
return {
reset
}
}
}
</script>

As you can see, we can use the ref attribute to get a reference to the Counter component, and then access its exposed stuff using the dot notation. In this case, we can use counter.value.count to get or set the count value, and counter.value.increment to call the increment method.

Why use defineExpose()?

You might be wondering why we need to use defineExpose() at all. After all, we can already access the public stuff of a child component using the $refs property in Vue 2. For example, we can rewrite the previous example without using defineExpose() like this:

<template>
<div>
<h1>Counter App</h1>
<Counter ref="counter" />
<button @click="reset">Reset</button>
</div>
</template>

<script>
import { ref } from 'vue'
import Counter from './Counter.vue'
export default {
components: {
Counter
},
setup() {
// get a reference to the Counter component
const counter = ref(null)
// define a method to reset the count
const reset = () => {
counter.value.$refs.count.value = 0
}
// return the method
return {
reset
}
}
}
</script>

However, there are some problems with using $refs:

  • It relies on a secret handshake between the parent and child components. The parent component needs to know the names and types of the stuff that the child component exposes, which may not be obvious or documented.
  • It breaks the privacy of the child component. The parent component can access any stuff of the child component, even if they are meant to be secret or internal. This can lead to weird side effects or bugs if the child component changes its inner workings.
  • It is not type-safe or IDE-friendly. The $refs property is a generic object that does not have any type information or code completion. This makes it easy to make typos or access non-existent stuff.

By using defineExpose(), we can solve these problems by:

  • Making the handshake between the parent and child components explicit and clear. The child component declares what it exposes, and the parent component can only access what is exposed.
  • Protecting the privacy of the child component. The parent component cannot access anything that is not exposed by the child component, which ensures that the child component can change its inner workings without affecting the parent component.
  • Improving the type-safety and IDE-friendliness. The exposed stuff are typed and can be auto-completed by the IDE, which reduces the chances of errors and improves the developer experience.

How to use defineExpose()?

To use defineExpose(), you need to follow these steps:

  1. Import defineExpose from ‘vue’ in your child component script.
  2. Call defineExpose() inside the setup() function of your child component, passing an object with the stuff you want to expose.
  3. Use the ref attribute to get a reference to the child component in your parent component template.
  4. Access the exposed stuff using the dot notation in your parent component script or template.

Here are some tips and best practices for using defineExpose():

  • Only expose what is necessary for the parent component to use. Avoid exposing too much or too little, as this can make your component hard to understand or reuse.
  • Use descriptive and consistent names for your exposed stuff. This can help the parent component to know what they do and how to use them.
  • Document your exposed stuff using comments or JSDoc. This can help the parent component to know their types, parameters, return values, and effects.
  • Use defineExpose() with caution when using TypeScript. Since defineExpose() is a runtime function, it does not affect the type inference or checking of your component. You may need to use type assertions or generics to ensure that your exposed stuff are correctly typed.
Photo by Markus Spiske on Unsplash

Conclusion

In this post, I have told you what defineExpose() is, why you should use it, and how to use it in your Vue 3 projects. I hope you have learned something new and funny from this post.

If you have any questions or feedback, please feel free to leave a comment below. Thank you for reading!👋

--

--