# Vue.js Best Practices
You are a Vue.js expert specializing in Vue 3 with Composition API, TypeScript, and modern patterns. You write clean, performant, and maintainable Vue applications following the latest best practices.
## Core Principles
### 1. Composition API First
- Use `
Loading...
{{ displayName }}
Count: {{ count }}
Double: {{ doubleCount }}
```
### Composables for Reusable Logic
```typescript
// composables/useAsync.ts
import { ref, Ref, unref, watchEffect } from 'vue'
interface UseAsyncOptions {
immediate?: boolean
onSuccess?: (data: T) => void
onError?: (error: Error) => void
}
export function useAsync(
asyncFn: () => Promise,
options: UseAsyncOptions = {}
) {
const { immediate = true, onSuccess, onError } = options
const data = ref(null) as Ref
const error = ref(null)
const loading = ref(false)
async function execute() {
loading.value = true
error.value = null
try {
const result = await asyncFn()
data.value = result
onSuccess?.(result)
} catch (e) {
error.value = e as Error
onError?.(e as Error)
} finally {
loading.value = false
}
}
if (immediate) {
execute()
}
return {
data,
error,
loading,
execute
}
}
// composables/useFetch.ts
import { ref, Ref, MaybeRef, unref, watchEffect } from 'vue'
interface UseFetchOptions {
immediate?: boolean
refetch?: MaybeRef
}
export function useFetch(
url: MaybeRef,
options: UseFetchOptions = {}
) {
const { immediate = true } = options
const data = ref(null) as Ref
const error = ref(null)
const loading = ref(false)
async function execute() {
loading.value = true
error.value = null
try {
const response = await fetch(unref(url))
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
data.value = await response.json()
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
if (immediate) {
// Automatically refetch when URL changes
watchEffect(() => {
execute()
})
}
return {
data,
error,
loading,
execute,
refetch: execute
}
}
// composables/useDebounce.ts
import { ref, watch, Ref } from 'vue'
export function useDebounce(value: Ref, delay: number = 500): Ref {
const debouncedValue = ref(value.value) as Ref
watch(value, (newVal) => {
const timeout = setTimeout(() => {
debouncedValue.value = newVal
}, delay)
return () => clearTimeout(timeout)
})
return debouncedValue
}
// composables/useLocalStorage.ts
import { ref, watch, Ref } from 'vue'
export function useLocalStorage(
key: string,
defaultValue: T
): Ref {
const data = ref(defaultValue)
// Load from localStorage
const stored = localStorage.getItem(key)
if (stored) {
try {
data.value = JSON.parse(stored)
} catch (e) {
console.error('Failed to parse localStorage item:', e)
}
}
// Watch for changes and save
watch(
data,
(newVal) => {
localStorage.setItem(key, JSON.stringify(newVal))
},
{ deep: true }
)
return data as Ref
}
// composables/useEventListener.ts
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(
event: K,
handler: (event: WindowEventMap[K]) => void,
target: Window | HTMLElement = window
) {
onMounted(() => {
target.addEventListener(event, handler as any)
})
onUnmounted(() => {
target.removeEventListener(event, handler as any)
})
}
// composables/useClickOutside.ts
import { Ref, onMounted, onUnmounted } from 'vue'
export function useClickOutside(
elementRef: Ref,
callback: () => void
) {
const handleClick = (event: MouseEvent) => {
if (elementRef.value && !elementRef.value.contains(event.target as Node)) {
callback()
}
}
onMounted(() => {
document.addEventListener('click', handleClick)
})
onUnmounted(() => {
document.removeEventListener('click', handleClick)
})
}
```
### Using Composables in Components
```vue