Vue 3 Script Setup Macros
Vue 3 Script Setup is the recommended syntax if you are using both Single File Components and Composition API. It provides a clean and…
Vue 3 Script Setup Macros
Vue 3 Script Setup is the recommended syntax if you are using both Single File Components and Composition API. It provides a clean and concise structure that significantly reduces boilerplate code.
The main idea behind composition API is to explicitly import every helper that you need to use. For example, in the following code, both ref and computed are imported from the Vue Core.
<script setup>
import { ref, computed } from "vue";
const counter = ref(0);
const isNegative = computed(() => counter.value < 0);
</script>
But there are some helpers that don’t need to be imported and are always available in our components. These helpers are called compiler macros. Let’s explore five of them: defineProps, defineEmits, defineExpose defineModeland defineOptions.
1. [defineProps](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)
This is an essential macro that you probably got familiar with during your first interactions with script setup. It allows you to declare props with the same syntax as in Options API but with full TypeScript type inference support.
<script setup>
defineProps({
name: String,
age: {
type: Number,
default: 18,
},
});
</script>
Every [best practice to declare and validate a prop](https://medium.com/js-dojo/validating-your-vue-props-like-a-pro-5a2d0ed2b2d6) still applies with this syntax.
Inside the template, every prop will be automatically available.
<script setup>
defineProps({
name: String,
});
</script>
<template>
{{ name }}
</template>
In case you want to use a prop inside the script setup you need to assign the return value of the macro to a variable.
<script setup>
const props = defineProps({
name: String,
});
console.log(props.name);
</script>
2. [defineEmits](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)
The defineEmits macro is similar to defineProps but is used to declare custom events emitted by the component. It ensures that emitted events are properly documented and provides type inference.
<script setup>
const emit = defineEmits(["update:name", "delete"]);
</script>
In this example, we use defineEmits to declare two custom events: 'update:name' and 'delete'. The emit variable returned by defineEmits allows you to emit these events within your component’s logic.
<script setup>
const emit = defineEmits(["delete"]);
</script>
<template>
<button @click="emit('delete')">Delete</button>
</template>
Emits can also be declared using pure-type syntax by passing a literal type argument to defineEmits:
const emit = defineEmits<{
(e: "change", id: number): void;
(e: "update", value: string): void;
}>();
// Vue 3.3+: alternative, more succinct syntax
const emit = defineEmits<{
change: [id: number]; // named tuple syntax
update: [value: string];
}>();
3. [defineExpose](https://vuejs.org/api/sfc-script-setup.html#defineexpose)
The defineExpose macro is a bit trickier. It is used to explicitly expose properties or methods from your component’s <script setup> block. By default, components using script setup are closed, meaning their internal bindings are not exposed to the parent component.
However, in some cases, you may want to make certain properties or methods accessible from the parent component.
<script setup>
const message = "Hello, world!";
const count = ref(0);
defineExpose({
message,
increment() {
count.value++;
},
});
</script>
In this example, we expose the message variable and the increment method to the parent component. They can be accessed from the parent using [a template ref](https://vuejs.org/guide/essentials/template-refs.html#template-refs):
<script setup>
import ChildComponent from "./ChildComponent.vue";
import { ref } from "vue";
const childComponentRef = ref(null);
</script>
<template>
<h1>Parent Component</h1>
<p>Message: {{ childComponentRef?.message }}</p>
<p>
Increase counter in the child component:
<button @click="childComponentRef.increment">+1</button>
</p>
<hr />
<ChildComponent ref="childComponentRef" />
</template>
This way the parent component can have access to the child component’s internal state and methods which is sometimes useful especially when developing a third-party library. But be aware that this pattern might cause component coupling. For our example moving the child’s state to a composable is probably a better choice.
4. [defineOptions](https://vuejs.org/api/sfc-script-setup.html#defineoptions) 🆕
The defineOptions macro allows you to declare additional component options inside the <script setup> block. This can be useful when you need to specify options that cannot be expressed using script setup macros alone.
<script setup>
defineOptions({
name: "ComponentName",
owner: "Team A",
inheritAttrs: false,
});
</script>
Note that defineOptions is introduced in Vue 3.3. In previous versions, we had to use two separate scripts.
<!-- Syntax before Vue 3.3 -->
<script setup>
// Component logic goes here
</script>
<script>
export default {
name: "ComponentName",
owner: "Team A",
inheritAttrs: false,
};
</script>
5. defineModel 🆕
This is another addition of Vue 3.3 that aims to simplify two-way data binding inside script setup reduce boilerplate and improve development experience.
Previously to support two-way binding with v-model, we needed to declare a prop and emit a corresponding update:propName event.
<!-- Syntax before Vue 3.3 -->
<script setup>
const props = defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
console.log(props.modelValue);
function onInput(e) {
emit("update:modelValue", e.target.value);
}
</script>
<template>
<input :value="modelValue" @input="onInput" />
</template>
We can do the same with the new defineModel macro. The macro automatically registers a prop and returns a ref that can be directly mutated.
<script setup>
const modelValue = defineModel();
console.log(modelValue.value);
</script>
<template>
<input v-model="modelValue" />
</template>
Conclusion
In conclusion, while the usefulness of these macros varies, each of them is essential when writing code in Vue 3 and Script Setup. You can find out more about them in the official Vue documentation and see some additional options in the Vue Macros external library.


