Collection of Vue Macros in Vue.js 3.3
Eduard Krivanek
Posted on May 28, 2023
Many developers (myself included) were excited when Vue.js version 3.3 introduced several functionalities from Vue Macros, the collection of proposed additions to Vue.js that extend its basic functionalities with syntactic sugar.
In this post, you will learn about Vue Macros that became a part of Vue version 3.3 and how you can use them if you are not yet on version 3.3.
What is Vue Macros?
Let's go straight to the source! The official website claims the following:
Vue Macros is a library that implements proposals or ideas that have not been officially implemented by Vue. That means it will explore and extend more features and syntax sugar to Vue.
Looking at its GitHub repo, the library is well maintained. Moreover, Vue Macros supports Vue from version 2.7. To install Vue Macros, you need to run npm i -D unplugin-vue-macros
, configure vite.config.ts
, and the tsconfig.json
as is installation suggests to start using them.
Which Macros do I want to use, you ask? Let’s look into Macros introduced as part of Vue version 3.3.
Macro defineModels
Prior to v3.3, when it came to component two-way binding with v-model
, we had to implement props
and emits
in the child component to receive or notify the parent component about a data change. You usually end up with the following example:
// MySearch.vue
<template>
<div>
<button
v-for="data in store.getLoadedElements"
:key="data.title"
@click="() => onInput(data)"
>
{{ data.title }}
</button>
</div>
</template>
<script setup lang="ts">
// some additional code
const store = useStore();
const props = defineProps({
modelValue: {
type: Element,
required: false
}
});
const emits = defineEmits<{
(e: "update:modelValue", value: Element): void;
}>();
function onInput(element: Element) {
emit('update:modelValue', element)
}
// some additional code
</script>
The defineModels macro is a syntactic sugar by which you can avoid implementing two-way binding for using v-model on the component level instead of the default Vue.js implementation. You can achieve the above logic with the following syntax:
// MySearch.vue
<template>
<div>
<button
v-for="data in store.getLoadedElements"
:key="data.title"
@click="modelValue = data"
>
{{ data.title }}
</button>
</div>
</template>
<script setup lang="ts">
// some additional code
const store = useStore();
const { modelValue } = defineModels<{ modelValue: Element }>();
// some additional code
</script>
Vue.js 3.3 recently received the defineModels
enhancement for two-way binding under the name defineModel, which has the exact same functionality.
You will find more information in our blog post: New in Vue.js 3.3: Two-Way Binding With defineModel Macro. However, opting for the defineModel
functionality in Vue.js 3.3, as it is still an experimental feature, you have to modify your vite.config.ts
in the following way:
plugins: [
vue({
script: {
defineModel: true
// ^^ enables the feature
}
})
]
Macro definePropsRefs
Reactive props destructuring was quite a requested feature from the Vue.js community. It’s safe to say the developer was excited about the feature released in Vue 3.3. However, the older Vue.js version still receives the following error message when trying to destructure props.
Fortunately, the macro definePropsRefs allows us property destructuring by writing the following code:
const { showModal, inputValue } = definePropsRefs({
showModal: {
type: Boolean,
required: false
},
inputValue: {
type: String,
required: false,
default: ""
}
});
Using Vue Macros are much better developer experience than destructuring properties before version 3.3:
const { showModal, inputValue } = toRefs(
withDefaults(
defineProps({
showModal: {
type: Boolean,
required: false
},
inputValue: {
type: String,
required: false,
default: ""
}
}),
{
showModal: false,
inputValue: ""
}
)
)
If you want to use defineProps
an experimental feature in Vue version 3.3, you have to opt-in with the following configuration:
// vite.config.js
plugins: [
vue({
script: {
propsDestructure: true
// ^^ enables the feature
}
})
]
Macro defineEmit
Using defineEmits
wasn’t such a bad experience. You declared and used emits in the following way:
const emit = defineEmits<{
(e: 'foo', id: number): void
(e: 'bar', name: string): void
}>()
emit('foo', 10);
emit('bar', '10'');
Using defineEmit from Vue Macros, you get the advantage of declaring individual emits in the following way:
<script setup>
// Declare emit
const foo = defineEmit<number[]>('foo')
// Infer emit name from variable name
const bar = defineEmit<string[]>()
// Emit array of strings
const test = defineEmit<string[][]>()
foo(10)
bar('10')
test(['test1', 'test2'])
</script>
Vue 3.3 brought a little bit of syntactic sugar, working with defineEmits in the following way:
const emit = defineEmits<{
foo: [id: number]
bar: [name: string]
}>()
Fortunately, there is no opting-in for this feature, so you can use it right away!
Macro defineSlots
When working with scoped slots in Vue.js, one problem we faced was not knowing what properties were available on the parent component. Here is the following example before Vue version 3.3:
<!-- <ChildComponent> template -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
// ------------------------------------------
<!-- <ParentComponent> template -->
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
A developer may access slotProps.anything
and receive an error only during the runtime.
Vue.js 3.3 introduced defineSlots
, which use types slots, eliminating the parent component of accessing non-existing props from the child. Here is the following example:
<!-- <ChildComponent> -->
<template>
<div v-for="item of items" :key="item">
<h2>Name</h2>
<!-- default slot -->
<slot name="itemNameSlot" :item-name="item">
{{ item }}
</slot>
</div>
</template>
<script setup lang="ts">
const items = ref<string[]>(["item1", "item2", "item3"]);
defineSlots<{
itemNameSlot: (props: { itemName: string }) => any;
}>();
</script>
// ----------------------------
<!-- <ParentComponent> -->
<template>
<main>
<Test>
<template #itemNameSlot="{ itemName }">
<a :href="`/someurl/${itemName}`">
{{ itemName }} - click
</a>
</template>
</Test>
</main>
</template>
If you replace the itemName
with a different value in the #itemNameSlot="{ itemName }"
in the parent component, you will receive the following error:
Fortunately, there is already a macro for the same functionality with the same name - defineSlots from Vue Macros if you are not yet on version 3.3.
Summary
Vue.js 3.3 brought new features that developers were waiting for. The community has already implemented most of these features in a package called Vue Macros. It is a small but convenient collection of utils that can be used even on Vue version 2.7.
Posted on May 28, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.