Here are 10 practical tips to for better Vue applications
<script setup>
for Cleaner Component CodeThe composition API has several advantages over the Options API including:
The <script setup>
syntax provides a the most intuitive and fluff-free way to use the Composition API:
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
<p>Double count is: {{ doubleCount }}</p>
</template>
This syntax reduces boilerplate and makes your components more readable. If you are familiar with the Options API and want to learn the composition API, checkout our Vue 3 Composition API. The transition is easier than you think!
defineProps
and defineEmits
for Type-Safe Props and EmitsUse defineProps
and defineEmits
with Typescript for better type inference:
<script setup lang="ts">
const props = defineProps<{
title: string
count?: number
}>()
const emit = defineEmits<{
change: [id: number];
update: [value: string];
}>();
// Usage
emit('change', 1)
</script>
This provides type safety for your component's external API meaning consumers of the component get top-notch autocomplete results and helpful error detection (ie, those wonderful red-squiggly lines) in their IDE’s
If you want to learn how to best leverage TypeScript with Vue.js, you can tune in to our course TypeScript with Vue.js 3. If you have 0 experience with TypeScript we also have a TypeScript fundamentals course.
toRefs
to Destructure Reactive ObjectsWhen you need to destructure a reactive object, use toRefs
to maintain reactivity:
<script setup>
import { toRefs } from 'vue'
const props = defineProps<{
title: string
count?: number
}>()
const { title, count } = toRefs(props)
console.log(title.value) // Will update when props changes
</script>
Extract reusable logic into composables:
// useCounter.ts
import { ref } from 'vue'
export function useCounter(initial = 0) {
const count = ref(initial)
function increment() {
count.value++
}
return { count, increment }
}
// Component.vue
<script setup>
import { useCounter } from './useCounter'
const { count, increment } = useCounter(10)
</script>
This promotes code reuse and keeps your components clean. This is one of the number 1 reasons I recommend the composition API over the options API. It’s more obvious when these composable abstractions can be made if you are writing components using the Composition API.
Learn how to create your own composables in our course dedicated to this topic. Or reach for one of the most popular open-source collections of composable called VueUse. We have a complete course on VueUse as well.
watchEffect
for Reactive Side EffectswatchEffect
runs a function immediately while reactively tracking its dependencies:
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
const message = ref('')
watchEffect(() => {
message.value = `The count is ${count.value}`
})
</script>
This is great for side effects that depend on reactive state. Learn more about watchEffect
from our Composition API course.
provide
and inject
for Deep Props PassingUse provide
and inject
to pass data deeply without props drilling:
<!-- Parent.vue -->
<script setup>
import { provide, ref } from 'vue'
const theme = ref('light')
provide('theme', theme)
</script>
<!-- DeepChild.vue -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme', 'light') // 'light' is the default value
</script>
This simplifies passing data to deeply nested components and can be handy for a number of practical use cases. Learn more in our Composition API course or checkout this article on creating tightly coupled components with provide/inject.
shallowRef
for Large ObjectsWhen dealing with large objects that don't need deep reactivity, use shallowRef
:
<script setup>
import { shallowRef } from 'vue'
const largeObject = shallowRef({ /* many properties */ })
function updateObject() {
largeObject.value = { /* new large object */ }
}
</script>
This can significantly improve performance for large, frequently updated objects.
defineExpose
to Control Component Public InterfaceWith <script setup>
, use defineExpose
to explicitly control what's exposed to parent components:
<!-- AppModal.vue -->
<script setup>
import { ref } from 'vue'
const isOpen = ref(false)
function open() {
isOpen.value = true
}
function close() {
isOpen.value = true
}
defineExpose({ open, close })
</script>
<!-- ParentComponent.vue -->
<script setup>
const modal = ref();
</script>
<template>
<button @click="modal.open()">Open Modal></button>
<AppModal ref="modal">
</template>
This gives you fine-grained control over your component's public API. Learn more about defineExpose and other advanced strategies for good component API design from our course: Advanced Components: Exposing Internal State.
effectScope
for Grouped Effects CleanupUse effectScope
to group and clean up multiple effects together:
import { effectScope, onScopeDispose } from 'vue'
const scope = effectScope()
scope.run(() => {
// All effects created here will be automatically disposed together
const data = ref(null)
watchEffect(() => {/* ... */})
watch(data, () => {/* ... */})
})
onScopeDispose(() => {
scope.stop() // stop all effects in the scope
})
This is particularly useful for creating composables that set up and tear down multiple effects.
By applying these Vue 3-specific tips, you can create more efficient, maintainable, and powerful Vue applications. Remember to always consider the specific needs of your project when applying these techniques.
It is possible to have 2 script sections within a Vue Single File component: one with the setup
attribute and one without. This can be helpful for a variety of reasons.
One of those reasons is exporting types or data that are tightly tied to the component but could be useful elsewhere.
<!-- UserProfileComponent -->
<script lang="ts">
export interface UserProfile{
username: string,
// etc...
}
</script>
<script setup lang="ts">
defineProps<UserProfile>()
</script>
Another is to export provide/inject keys for tightly coupled components.
Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.