Vue 3 Best Practices
Quick Reference
| Topic | When to Use | Reference | |-------|-------------|-----------| | TypeScript | Props extraction, generic components, useTemplateRef, JSDoc, reactive props destructure | typescript.md | | Volar | IDE config, strictTemplates, CSS modules, directive comments, Volar 3.0 migration | volar.md | | Components | defineModel, deep watch, onWatcherCleanup, useId, deferred teleport | components.md | | Tooling | moduleResolution, HMR SSR, duplicate plugin detection | tooling.md | | Testing | Pinia store mocking, setup stores, Vue Router typed params | testing.md |
Essential Patterns
Extract Component Props
import type { ComponentProps } from 'vue-component-type-helpers'
import MyButton from './MyButton.vue'
type Props = ComponentProps<typeof MyButton>
Reactive Props Destructure (Vue 3.5+)
<script setup lang="ts">
// Destructured props are reactive - preferred in Vue 3.5+
const { name, count = 0 } = defineProps<{ name: string; count?: number }>()
</script>
useTemplateRef (Vue 3.5+)
<script setup lang="ts">
import { useTemplateRef, onMounted } from 'vue'
const inputRef = useTemplateRef('input') // Auto-typed
onMounted(() => inputRef.value?.focus())
</script>
<template><input ref="input" /></template>
onWatcherCleanup (Vue 3.5+)
import { watch, onWatcherCleanup } from 'vue'
watch(query, async (q) => {
const controller = new AbortController()
onWatcherCleanup(() => controller.abort())
await fetch(`/api?q=${q}`, { signal: controller.signal })
})
defineModel with Required
// Returns Ref<Item> instead of Ref<Item | undefined>
const model = defineModel<Item>({ required: true })
Deep Watch with Numeric Depth
// Vue 3.5+ - watch array mutations without full traversal
watch(items, handler, { deep: 1 })
Pinia Store Test Setup
import { createTestingPinia } from '@pinia/testing'
import { vi } from 'vitest'
mount(Component, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })]
}
})
Common Mistakes
- Using
InstanceType<typeof Component>['$props']- UseComponentPropsinstead - Missing
createSpyin createTestingPinia - Required in @pinia/testing 1.0+ - Using
withDefaultswith union types - Use Reactive Props Destructure strictTemplatesin wrong tsconfig - Add totsconfig.app.json, not root- ts_ls with Volar 3.0 - Use vtsls instead (Neovim)
deep: trueon large structures - Use numeric depth for performance- Watching destructured props directly - Wrap in getter:
watch(() => count, ...) - Random IDs in SSR - Use
useId()for hydration-safe IDs