Guides & Tutorials
Understanding the new script setup with defineProps & defineEmits in Vue 3.2
Vue 3 introduced us to the Composition API - a different way of handling reactive data using ref
and reactive
in Vue.js. It received a lot of positive feedback but also some concerns about the verbosity of the Composition API usage inside SFCs. Then comes Vue 3.2 with a stable release of a new feature <script setup>
which aims to address those concerns in a very practical way.
Why am I excited about <script setup>
? It made the already simple Composition API even simpler. With <script setup>
, we don't need named or default exports in SFCs anymore, we can simply define variables and use them in the template.
Getting started with script setup
This is probably the easiest part. To start using <script setup>
, all you need to do is add setup
to your existing script tag like this:
<script setup>
import { ref } from 'vue'
const name = ref('Joe')
</script>
And you can access name in the template like this:
<script setup>
import { ref } from 'vue'
const name = ref('Joe')
</script>
<template>
<h1>{{ name }}</h1>
</template>
That's it. This is simple and very useful in reducing code bulk in larger codebases. To achieve the same result with the Composition API, we'll need to return the variable (i.e expose it to the template) like this:
<script>
import { ref } from 'vue'
export default {
setup() {
const name = ref('Joe')
return {
name
}
}
}
</script>
<template>
<h1>{{ name }}</h1>
</template>
You can immediately get a sense of how large a codebase can grow when you have large SFCs. With script setup
, we get rid of all the unnecessary boilerplate code and trim our component down to only what is needed.
Note, we used a name
variable for simplicity in the script setup
snippet, but you're not limited to just variables. Anything (including helper functions) declared inside the script setup
context will be accessible from the template.
<script setup>
import { greet } from './utils/greeting'
</script>
<template>
<div>{{ greet('good morning!') }}</div>
</template>
But that's not all, we can also import and use components without any extra config or bindings. For instance, consider importing a <SubscriptionForm />
component into the App.vue
file. Without the script setup
feature, you'll probably do something like this:
// App.vue
<script>
import SubscriptionForm from './components/SubscriptionForm'
export default {
components: { SubscriptionForm },
setup(){
return {}
}
}
</script>
<template>
<SubscriptionForm />
</template>
This is the pattern I imagine you would be familiar with. But it gets better. With script setup
you can import the component and use it in the template with no additional step, like this:
// App.vue
<script setup>
import SubscriptionForm from './components/SubscriptionForm'
</script>
<template>
<SubscriptionForm />
</template>
We didn't need to declare a components
object and register the SubscriptionForm
component in it as we did before. Cool right?
A good question to ask at this point would be, so we've imported the SubscriptonForm />
component into App.vue
, how do we pass props to it? or emit an event from it to tell the parent that the form has been submitted? Let's find out.
The script setup
feature ships with the defineProps
and defineEmits
APIs that make it possible for us to declare props and emits. They are automatically available inside the script setup
context and doesn't need to be imported to use them. Let's demonstrate!
First, in the <SubscriptionForm />
component, lets define a prop and also emit the submit
event like so:
// SubscriptionForm.vue
<template>
<form @submit.prevent="subscribe" v-if="props.age">
<label>Email
<input v-model="email" type="email"/>
</label>
<button>Subscribe</button>
</form>
</template>
<script setup>
import { computed } from "@vue/reactivity"
const props = defineProps({
age: {
type: Number,
required: true
}
})
const email = computed({
get() {
return props.email
},
set(value) {
emit('subscribe:user', value)
}
})
const emit = defineEmits(['subscribe:user', 'subscribe'])
function subscribe() {
emit('subscribe')
}
</script>
In the snippet above, we did a couple of things using the defineProps
and defineEmits
API:
-
defineProps – allows us to define props for our component. We used it to define the
age
prop that will get passed in from the parent (App.vue). Our form will only be visible IF the age prop exists. -
defineEmits – lets us define the events that our component can emit. In this case, we emit a
subscribe
event to let the parent component (App.vue) know when the form has been submitted. When that happens, we just log "form submitted" on the parent.
Next, lets update App.vue to capture all these code updates:
// App.vue
<script setup>
import SubscriptionForm from './SubscriptionForm'
function subscribeUser() {
console.log("form submitted! do something");
}
</script>
<template>
<SubscriptionForm age=12 @submit="subscribeUser" />
</template>
Here, we pass the age
prop to the SubsbscriptionForm />
component and set up our submit
event to call the subscribeUser
function whenever the form is submitted from the child component.
And that is how we use props and events in the script setup
context.
Worthy mentions
As you can imagine, other features shipped with script setup
that we did not get to in this post. However, one that I find really worthy of mentioning is the dynamic components feature. It allows you to dynamically render components in your Vue templates when certain conditions are satisfied.
<script setup>
import Profile from './Profile.vue'
import LoginForm from './LoginForm.vue'
</script>
<template>
<component :is="userLoogedIn ? Profile : LoginForm" />
</template>
If you'd like to explore more features and learn other things about the Vue script setup
feature, feel free to read the documentation here.