Home / Blog / 10 Practical Tips for Better Vue Apps
10 Practical Tips for Better Vue Apps

10 Practical Tips for Better Vue Apps

Daniel Kelly
Daniel Kelly
September 9th 2024

Here are 10 practical tips to for better Vue applications

1. Use the Composition API and <script setup> for Cleaner Component Code

The composition API has several advantages over the Options API including:

  • Better organization by logical concern
  • Easier stateful logic re-use
  • And improved typesafety

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!

2. Leverage defineProps and defineEmits for Type-Safe Props and Emits

Use 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

typesafe-autocomplete-results

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.

3. Use toRefs to Destructure Reactive Objects

When 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>

4. Create Composables for Reusable Logic

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.

5. Use watchEffect for Reactive Side Effects

watchEffect 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.

6. Leverage provide and inject for Deep Props Passing

Use 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.

7. Use shallowRef for Large Objects

When 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.

8. Use defineExpose to Control Component Public Interface

With <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.

9. Leverage effectScope for Grouped Effects Cleanup

Use 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.

10. Use 2 Script Tags in Single File Components

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.

Start learning Vue.js for free

Daniel Kelly
Daniel Kelly
Daniel is the lead instructor at Vue School and enjoys helping other developers reach their full potential. He has 10+ years of developer experience using technologies including Vue.js, Nuxt.js, and Laravel.

Comments

Latest Vue School Articles

How to Access Vue Refs Defined in Script Setup within Unit Tests

How to Access Vue Refs Defined in Script Setup within Unit Tests

Need to access a component’s data defined within script setup? In this article we’ll teach you how! But be warned you probably want to approach your test a little differently.
Daniel Kelly
Daniel Kelly
48 Hours of Unlimited Vue.js Learning: Your Guide to Vue School&#8217;s Free Weekend

48 Hours of Unlimited Vue.js Learning: Your Guide to Vue School’s Free Weekend

Vue School’s Free Weekend is coming! For 48 hours, you’ll get free access to over 1300 lessons across 65 expert courses. Whether you’re a beginner or advanced, this is your chance to explore new Vue.js topics.
Maria Panagiotidou
Maria Panagiotidou

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!

Follow us on Social

© All rights reserved. Made with ❤️ by BitterBrains, Inc.