Vue 3 Tips & Best Practices i 2026
Vue 3 har udviklet sig enormt siden de første releases. Med Vue 3.4 og 3.5 har vi fået en række nye features, der gør det nemmere og mere elegant at bygge moderne webapplikationer. I denne artikel deler jeg mine bedste tips og best practices baseret på 19+ års erfaring med frontend-udvikling.
Script Setup er standarden
Hvis du stadig bruger Options API eller den lange setup() funktion, er det tid til at skifte. <script setup> er den anbefalede måde at skrive Vue 3 komponenter på. Det giver dig renere kode, bedre TypeScript-understøttelse og automatisk eksponering af variabler til din template.
<script setup lang="ts">
import { ref, computed } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
Count: {{ count }} (doubled: {{ doubled }})
</button>
</template>
Alt der er deklareret i <script setup> er automatisk tilgængeligt i din template. Ingen return-statement, ingen defineComponent wrapper. Rent og enkelt.
defineModel - to-vejs binding gjort nemt (Vue 3.4+)
En af de bedste tilføjelser i Vue 3.4 er defineModel(). Før skulle du manuelt håndtere props og emits for v-model bindings. Nu er det en one-liner:
<!-- ChildInput.vue -->
<script setup lang="ts">
const modelValue = defineModel<string>()
</script>
<template>
<input v-model="modelValue" />
</template>
<!-- ParentForm.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import ChildInput from './ChildInput.vue'
const name = ref('')
</script>
<template>
<ChildInput v-model="name" />
<p>Du skrev: {{ name }}</p>
</template>
Du kan også bruge navngivne models og flere models på samme komponent:
<script setup lang="ts">
const firstName = defineModel<string>('firstName')
const lastName = defineModel<string>('lastName')
</script>
defineSlots - typesikre slots (Vue 3.3+)
Med defineSlots() kan du definere typer for dine slots, hvilket giver fuld TypeScript-understøttelse og autocompletion:
<script setup lang="ts">
const slots = defineSlots<{
default(props: { item: string; index: number }): any
header(props: { title: string }): any
}>()
</script>
Det er særligt nyttigt når du bygger genbrugelige komponenter, hvor andre udviklere skal vide præcis hvilke slot props der er tilgængelige.
Reactive Destructuring (Vue 3.5+)
Vue 3.5 introducerede reactive destructuring af props - en feature mange har ventet på. Før mistede du reaktiviteten, når du destructurede props. Nu virker det bare:
<script setup lang="ts">
const { title, count = 0 } = defineProps<{
title: string
count?: number
}>()
// title og count er stadig reaktive!
// Du kan bruge dem direkte i computed, watch, etc.
</script>
Det gør koden mere læsbar og giver dig mulighed for at sætte default-værdier direkte i destructuring.
Composables - den rigtige måde at genbruge logik
Composables har fuldstændig erstattet mixins i Vue 3. De er nemmere at teste, har bedre TypeScript-support og undgår navnekollisioner. Her er et praktisk eksempel:
// composables/useLocalStorage.ts
import { ref, watch } from 'vue'
export function useLocalStorage<T>(key: string, defaultValue: T) {
const stored = localStorage.getItem(key)
const data = ref<T>(stored ? JSON.parse(stored) : defaultValue)
watch(data, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
return data
}
<script setup lang="ts">
import { useLocalStorage } from '@/composables/useLocalStorage'
const theme = useLocalStorage('theme', 'light')
const favorites = useLocalStorage<string[]>('favorites', [])
</script>
Gode composable-principper:
- Navngiv dem altid med
use-prefix - Return refs eller reactive objekter så forbrugeren kan destructure
- Hold dem fokuserede på én ting
- Håndter cleanup i
onUnmountedhvis nødvendigt
Pinia fremfor Vuex
Vuex er officielt deprecated. Pinia er den anbefalede state management-løsning for Vue 3. Den er lettere, har bedre TypeScript-support og bruger Composition API:
// stores/useUserStore.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null)
const isLoggedIn = computed(() => user.value !== null)
async function login(email: string, password: string) {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
})
user.value = await response.json()
}
function logout() {
user.value = null
}
return { user, isLoggedIn, login, logout }
})
Brug altid setup store syntaksen (som ovenfor) fremfor options syntaksen. Den er mere fleksibel og mapper direkte til Composition API patterns, du allerede kender.
TypeScript med Vue 3
Vue 3 er bygget med TypeScript, og understøttelsen er førsteklasses. Her er de vigtigste patterns:
<script setup lang="ts">
// Typer dine props med generics
const props = defineProps<{
title: string
items: BlogPost[]
variant?: 'primary' | 'secondary'
}>()
// Typer dine emits
const emit = defineEmits<{
submit: [value: string]
update: [id: number, data: Partial<BlogPost>]
}>()
// Typer dine refs
const inputRef = ref<HTMLInputElement | null>(null)
const posts = ref<BlogPost[]>([])
// Brug interfaces til komplekse typer
interface BlogPost {
id: number
title: string
content: string
publishedAt: Date
}
</script>
Et godt tip: Definer dine typer i separate filer under en types/ mappe, så de kan genbruges på tværs af komponenter og composables.
Performance Tips
Brug shallowRef til store objekter
Hvis du har store objekter eller arrays, der ikke har brug for dyb reaktivitet, kan shallowRef spare dig for betydelig overhead:
import { shallowRef, triggerRef } from 'vue'
// Kun top-level .value ændringer trigger opdateringer
const largeList = shallowRef<DataItem[]>([])
// Når du opdaterer nested data, trigger manuelt
largeList.value[0].name = 'Updated'
triggerRef(largeList)
// Eller erstat hele værdien
largeList.value = [...largeList.value]
Computed vs methods
computed caches resultatet og genberegner kun når afhængigheder ændres. Brug det altid til afledte værdier:
// GODT - caches resultatet
const expensiveResult = computed(() => {
return items.value.filter(i => i.active).sort((a, b) => b.score - a.score)
})
// SKIDT - kører ved hver re-render
function getExpensiveResult() {
return items.value.filter(i => i.active).sort((a, b) => b.score - a.score)
}
Lazy loading med defineAsyncComponent
Split din bundle ved at lazy-loade tunge komponenter:
import { defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent(() =>
import('./components/HeavyChart.vue')
)
// Med loading og error states
const HeavyEditor = defineAsyncComponent({
loader: () => import('./components/HeavyEditor.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorDisplay,
delay: 200,
timeout: 10000
})
Almindelige fejl du bør undgå
1. Muter ikke props
Vue advarer dig, men det er værd at gentage. Klon altid data fra props før du ændrer den:
const props = defineProps<{ user: User }>()
// FORKERT
props.user.name = 'New name'
// RIGTIGT
const localUser = ref({ ...props.user })
2. Glem ikke at bruge i v-for
Unikke keys hjælper Vue med effektivt at opdatere DOM:
<!-- Brug altid en unik identifier, aldrig index -->
<div v-for="item in items" :key="item.id">
{{ item.name }}
</div>
3. Undgå watch når computed er nok
Mange udviklere bruger watch hvor en computed ville være bedre:
// OVERKOMPLICERET
const fullName = ref('')
watch([firstName, lastName], ([first, last]) => {
fullName.value = `${first} ${last}`
})
// BEDRE
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
4. Håndter cleanup i composables
Hvis din composable opretter event listeners eller timers, ryd altid op:
export function useWindowResize() {
const width = ref(window.innerWidth)
function onResize() {
width.value = window.innerWidth
}
onMounted(() => window.addEventListener('resize', onResize))
onUnmounted(() => window.removeEventListener('resize', onResize))
return { width }
}
Opsummering
Vue 3 i 2026 er et modent og kraftfuldt framework. De nyeste features som defineModel, reactive destructuring og forbedret TypeScript-support gør det til en fornøjelse at arbejde med. Fokuser på at bruge <script setup>, skriv composables i stedet for mixins, vælg Pinia til state management og brug TypeScript overalt.
Har du brug for hjælp med din Vue 3 applikation, eller overvejer du at modernisere din eksisterende frontend? Tag kontakt med mig - jeg hjælper gerne med alt fra arkitektur og code reviews til fuld implementering.
Har du brug for en erfaren udvikler?
Lad os tage en snak om hvordan jeg kan hjælpe med teknisk strategi, arkitektur og frontend-udvikling.
Kontakt mig
Nicky Christensen