Vue Composables Skill
Patterns for creating reusable Vue 3 composables with proper TypeScript typing.
When to Use This Skill
- •Creating reusable reactive logic
- •Sharing state between components
- •Implementing custom lifecycle hooks
- •Managing async operations with cleanup
- •Building utility composables
Reference Documentation
For detailed patterns and conventions, see:
Quick Reference
Basic Composable Structure
typescript
// frontend/src/composables/useFeature.ts
import { ref, computed, onMounted, onUnmounted } from 'vue'
export function useFeature(initialValue: string) {
// Reactive state
const value = ref(initialValue)
const isLoading = ref(false)
// Computed
const isEmpty = computed(() => value.value.length === 0)
// Methods
function updateValue(newValue: string) {
value.value = newValue
}
async function fetchData() {
isLoading.value = true
try {
// async operation
} finally {
isLoading.value = false
}
}
// Lifecycle
onMounted(() => {
fetchData()
})
// Return public API
return {
value,
isLoading,
isEmpty,
updateValue,
fetchData
}
}
Critical Rules
- •Prefix with
use(e.g.,useTaskFilter) - •Return reactive refs not raw values
- •Clean up side effects in
onUnmounted - •Type all parameters and returns
- •Keep composables focused on a single concern
With Cleanup
typescript
export function useEventListener(
target: EventTarget,
event: string,
handler: EventListener
) {
onMounted(() => {
target.addEventListener(event, handler)
})
onUnmounted(() => {
target.removeEventListener(event, handler)
})
}
With Async and Error Handling
typescript
export function useAsync<T>(asyncFn: () => Promise<T>) {
const data = ref<T | null>(null)
const error = ref<Error | null>(null)
const isLoading = ref(false)
async function execute() {
isLoading.value = true
error.value = null
try {
data.value = await asyncFn()
} catch (e) {
error.value = e instanceof Error ? e : new Error(String(e))
} finally {
isLoading.value = false
}
}
return { data, error, isLoading, execute }
}
Shared State Pattern
typescript
// Shared state outside function = singleton
const globalCount = ref(0)
export function useGlobalCounter() {
function increment() {
globalCount.value++
}
return {
count: readonly(globalCount),
increment
}
}
File Naming
- •Composables:
useFeatureName.ts(e.g.,useTaskFilter.ts) - •Location:
frontend/src/composables/ - •Tests:
useFeatureName.spec.tsalongside