Vue 3 Tips & Best Practices
I’ll share my knowledge in giving you tips and the best practices when working with the latest version of Vue, Vue3.

If you haven’t made the transition to Vue 3 just yet, I recommend you check out my article on Vue 2! Even if you have made the transition, there are still some tips that may apply to Vue 3
Personally, I made the switch to Vue 3 about a year ago, after having been working with Vue 2 for several years.
My team and I were starting out a huge rewrite of an old AngularJS platform, and we decided to build the new and improved platform on Vue 3. The past year has been a lot of learning in regards to Vue 3. As a result of this, I would like to share my advice, tips, and best practices with you, if you’re in the moment of building a new Vue application.
Ref vs Reactive
There is a lot of discussions out there about when to use what. I find that when working with primitives, always go with ref(). When working with objects, go with reactive()
//When working with primitives like string, numbers etc:
const myName = ref('Nicky')//When working with objects:
const user = reactive({
name: 'Nicky',
age: 37
});Computed functions
Generally, computed functions in Vue are just awesome. They are cached based on their reactive dependencies and they are fast! I would arguably always try to convert something into a computed function if possible.
Consider this the following: You are binding one or multiple classes to a div element. The classes can change upon some state changing in your application. Usually, we would do the following:
<div
:class="{
invalid: !isValid,
valid: isValid,
error: isError
}"
>
Some content
</div>The better solution for this would be to convert it into a computed function like so:
const myComputedClasses = computed(() => {
return {
invalid: !isValid.value, //A reactive variable in my state
valid: isValid.value, //A reactive variable in my state
error: isError.value //A reactive variable in my state
}
})Computed functions are also super powerful when you eg: need to output a list of items that can see filtered:
const filteredBlogPosts = computed(() => {
return blogPosts.value.filter((blogPost) => {
return blogPost.title.toLowerCase().includes(searchString.value.toLowerCase())
})
})Make use of :key in v-for
When working with loops in our template always remember to make use of :key=””
<MySection v-for="section in sections" :key="section.id" :section="section" />Giving the item a key helps Vue if it needs to update components within the loop. The key is used as an identifier. Be aware that you shouldn’t have duplicate keys! A key should always be unique
Overwrite whole reactive objects without breaking reactivity
Sometimes you might find yourself in need of overwriting a whole reactive object. Unfortunately, this can be a bit tricky in Vue3, as you are likely to break the reactivity. Consider the following:
const task = reactive<Task>({
title: '',
daysAfterCourseStart: 1,
});const fetchTask = async () => {
isLoadingTask.value = true;
try {
const loadedTask = await getDocumentFromId<Task>(`task_templates/1234/tasks`, 'g12378123213d');
task = loadedTask;
} catch (e) {
console.log(e);
}
};This won’t work as you would expect as you can’t overwrite the whole task object without it losing reactivity. Instead, you should make use of Object.assign()
const fetchTask = async () => {
isLoadingTask.value = true;
try {
const loadedTask = await getDocumentFromId<Task>(`task_templates/1234/tasks`, 'g12378123213d');
Object.assign(task, loadedTask); //This works
} catch (e) {
console.log(e);
}
};Avoid directly DOM manipulation
When working with Vue, directly DOM manipulation is a no-go
yourDiv.className.add('my-class')Instead, you want to make use of refs, like so:
<template>
<div class="my-div" ref="myDivRef">
....
</div>
</template><script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: 'Index',
setup() {
const myDivRef = ref<HTMLElement>();
onMounted(() => {
myDivRef.value.classList.add('my-class');
})
}
});
</script>Access your props within setup
In Vue2, when you had to access a prop passed on to a component, you would like so something like: this.myProp to get the value of the prop.
This would be how you do it in the Options API which Vue 2 is using. In Vue 3, we have a new API, Composition API. In this it’s slightly different on how to access your props:
props: {
label: {
type: String
},
},
setup(props) {
console.log(props.label);
}Parent / Child communication
In the composition API, it is also possible to emit values back to the parent from the child component by using the emit() function in context.
props: {
label: {
type: String
},
},
setup(props, context) {
console.log(props.label);
context.emit('myLabel', props.label)
}Reuse of functionality
One of the major things in Vue 3 is how to share functionality across components. In Vue 2 we had something called Mixins which has been deprecated in Vue 3. Instead, we can make use of composeables. This is the new way of doing reuse of functionality. Learning how composeables work, can be its own article, so to keep this article more digestible, I will recommend you learn more about composeables here (https://vueschool.io/articles/vuejs-tutorials/what-is-a-vue-js-composable/)! Just know that composeables are a key part and one of the absolutely most cool features of Vue 3 — Learn them and learn them well. It will keep your code cleaner!
Bind style directly to state / data
One thing which has been tricky before is that you couldn’t bind directly to your CSS with your state. You can now in Vue 3. This is super cool and very useful in many cases
........
const color = ref('#f000');<style>
.text {
color: v-bind(color);
}
</style>Async Components in Vue 3
You should try making use of Async Components. This can dramatically speed up your application as you don’t pollute your main bundle with code you don't necessarily need. The cool thing about async functions is you only load the code when the client needs it and thereby keeping your JS bundle to a minimum
//From the vue docs > https://v3.vuejs.org/guide/migration/async-components.html#introductionimport { defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'
// Async component without options
const asyncModal = defineAsyncComponent(() => import('./Modal.vue'))
// Async component with options
const asyncModalWithOptions = defineAsyncComponent({
loader: () => import('./Modal.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
})Dont mutate props
If you try mutating props in Vue, you will likely get an error from your IDE, or from Vue itself. The reason for this is that mutating props, can cause unexpected side effects in your application. If you really need to change the value of props passed to a component, you should clone the object!
props: {
user: {
type: Object as PropType<UserData>
},
},
setup(props, context) {
const myUser = reactive({
...props.user
})
return {
myUser
}
}Use v-deep to overwrite styles
Sometimes you might find yourself using a third party package or component which has it’s own scoped style. You can make use of the v-deep selector to overwrite styles
::v-deep(.some-selector-in-child-component) { font-weight: bold; }//Deeper explanation here: https://v3.vuejs.org/api/sfc-style.html#style-scopedThanks for reading and I hope you liked the article, if so, please help support me by hitting that clap button or subscribing.
If you’re not a Medium member yet, consider becoming one! You get access to great content like this from thousands of authors! It helps support writers, and you have the chance to make money with your writing as well. Sign up here for only $5 a month.
If you want to subscribe my all of my content, you can do that here
If you’d like to catch up with me sometime, follow me on Twitter | LinkedIn or simply visit my website (That is however in Danish)






