1. Built-in Directives
<template>
<!-- v-show vs v-if -->
<div v-show="isVisible">Toggle display (CSS)</div>
<div v-if="isLoggedIn">Welcome!</div>
<div v-else>Please login</div>
<!-- v-for with key -->
<div v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</div>
<!-- v-bind shorthand -->
<div :class="{ active: isActive, 'text-bold': isBold }">...</div>
<div :style="{ color: textColor, fontSize: size + 'px' }">...</div>
<component v-bind="dynamicProps" />
<!-- v-on modifiers -->
<form @submit.prevent="handleSubmit">...</form>
<button @click.stop="handleClick">Stop propagation</button>
<input @keydown.enter.exact="submit" />
<div @click.self="handleSelf">Only direct clicks</div>
</template>
2. Custom Directives
// directives/vFocus.ts
import type { Directive } from 'vue'
export const vFocus: Directive = {
mounted(el: HTMLElement) {
el.focus()
},
}
// directives/vClickOutside.ts
export const vClickOutside: Directive = {
mounted(el, binding) {
el._clickOutside = (event: Event) => {
if (!el.contains(event.target as Node)) {
binding.value(event)
}
}
document.addEventListener('click', el._clickOutside)
},
unmounted(el) {
document.removeEventListener('click', el._clickOutside)
},
}
<script setup lang="ts">
import { vFocus } from '@/directives/vFocus'
import { vClickOutside } from '@/directives/vClickOutside'
function closeDropdown() {
isOpen.value = false
}
</script>
<template>
<input v-focus placeholder="Auto focused" />
<div v-click-outside="closeDropdown">Dropdown content</div>
</template>
3. Template Refs
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue'
const inputRef = ref<HTMLInputElement | null>(null)
const containerRef = ref<HTMLDivElement | null>(null)
onMounted(() => {
inputRef.value?.focus()
})
async function addItem() {
items.value.push(newItem)
// DOM chưa update — cần nextTick
await nextTick()
containerRef.value?.scrollTo({ top: containerRef.value.scrollHeight })
}
</script>
<template>
<input ref="inputRef" />
<div ref="containerRef" class="scroll-container">...</div>
</template>
4. Teleportation
<!-- Modal.vue — render outside component tree -->
<script setup lang="ts">
defineProps<{ isOpen: boolean }>()
defineEmits<{ (e: 'close'): void }>()
</script>
<template>
<Teleport to="body">
<div v-if="isOpen" class="modal-overlay" @click.self="$emit('close')">
<div class="modal-content">
<slot />
<button @click="$emit('close')">Đóng</button>
</div>
</div>
</Teleport>
</template>
5. KeepAlive & Dynamic Components
<script setup lang="ts">
import { shallowRef } from 'vue'
import TabA from './TabA.vue'
import TabB from './TabB.vue'
import TabC from './TabC.vue'
const currentTab = shallowRef(TabA)
const tabs = [
{ label: 'Tab A', component: TabA },
{ label: 'Tab B', component: TabB },
{ label: 'Tab C', component: TabC },
]
</script>
<template>
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.label"
@click="currentTab = tab.component"
>
{{ tab.label }}
</button>
</div>
<!-- KeepAlive caches component state -->
<KeepAlive :max="5">
<component :is="currentTab" />
</KeepAlive>
</template>
6. Suspense
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
Next article: Vue Router Deep Dive — routing, guards, lazy loading.