Composables
// useCounter.ts
export function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => count.value++
const reset = () => (count.value = initial)
return { count, increment, reset }
}
defineModel (Vue 3.4+)
<script setup>
const model = defineModel<string>({ required: true })
</script>
<template>
<input v-model="model" />
</template>
defineProps / defineEmits
const props = defineProps<{
title: string
count?: number
}>()
// With defaults
const props = withDefaults(defineProps<{ count?: number }>(), {
count: 0,
})
const emit = defineEmits<{
update: [value: string]
close: []
}>()
emit('update', 'new value')
Watchers
// Watch reactive source
watch(count, (newVal, oldVal) => { ... })
// Watch multiple
watch([a, b], ([newA, newB]) => { ... })
// Immediate + deep
watchEffect(() => { console.log(user.name) }) // auto-tracks dependencies
// Once
watch(source, handler, { once: true })
Lifecycle Hooks
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
} from 'vue'
onMounted(() => {
// DOM is ready
})
onBeforeUnmount(() => {
// cleanup: clear timers, remove event listeners
})
onErrorCaptured((err, instance, info) => {
// catch errors from child components
return false // prevent propagation
})
| Hook | When it runs |
|---|---|
onBeforeMount | Before first render |
onMounted | After first render, DOM available |
onBeforeUpdate | Before re-render on reactive change |
onUpdated | After re-render |
onBeforeUnmount | Before component is destroyed |
onUnmounted | After component is destroyed |
onErrorCaptured | When a child throws |
Template Refs
<script setup>
import { useTemplateRef, onMounted } from 'vue'
// Vue 3.5+ preferred
const inputEl = useTemplateRef<HTMLInputElement>('myInput')
onMounted(() => {
inputEl.value?.focus()
})
</script>
<template>
<input ref="myInput" />
</template>
Pre-3.5 approach (still works):
const inputEl = ref<HTMLInputElement | null>(null)
// same ref="myInput" in template
v-model Modifiers
<!-- Sync on change event instead of input -->
<input v-model.lazy="text" />
<!-- Auto-cast to number -->
<input v-model.number="age" type="number" />
<!-- Trim whitespace -->
<input v-model.trim="username" />
useAttrs / useSlots
const attrs = useAttrs() // non-prop attributes passed to component
const slots = useSlots() // slot functions
// Check if a slot was provided
const hasFooter = !!slots.footer
Disable automatic attribute inheritance (useful for custom root elements):
<script setup>
defineOptions({ inheritAttrs: false })
</script>
<template>
<div>
<input v-bind="$attrs" /> <!-- manually apply attrs to inner element -->
</div>
</template>
Provide / Inject
// parent
const key = Symbol() as InjectionKey<Ref<number>>
provide(key, ref(42))
// child
const val = inject(key) // typed as Ref<number> | undefined