Директивы — специальные атрибуты с префиксом v-, дающие Vue-компилятору инструкции по управлению DOM. Встроенные директивы: v-if, v-for, v-model, v-show.
**Кастомные директивы** позволяют инкапсулировать логику работы с DOM-элементами в переиспользуемую единицу.
const app = createApp(App)
app.directive('focus', {
mounted(el) {
el.focus()
}
})<script setup>
// В <script setup> директивы начинающиеся с 'v' автоматически распознаются
const vFocus = {
mounted(el) { el.focus() }
}
</script>
<template>
<input v-focus />
</template>app.directive('my-directive', {
// Перед вставкой элемента в DOM
beforeMount(el, binding, vnode) {},
// После вставки элемента в DOM (самый частый)
mounted(el, binding, vnode) {},
// Перед обновлением компонента
beforeUpdate(el, binding, vnode, prevVnode) {},
// После обновления компонента и дочерних
updated(el, binding, vnode, prevVnode) {},
// Перед размонтированием
beforeUnmount(el, binding, vnode) {},
// После размонтирования
unmounted(el, binding, vnode) {},
})<!-- v-директива:аргумент.модификатор="значение" -->
<div v-highlight:background.animate="'#ff0000'"></div>app.directive('highlight', {
mounted(el, binding) {
console.log(binding.value) // '#ff0000' — переданное значение
console.log(binding.arg) // 'background' — аргумент
console.log(binding.modifiers) // { animate: true } — модификаторы
console.log(binding.oldValue) // предыдущее значение (в updated)
console.log(binding.instance) // экземпляр компонента
}
})app.directive('focus', {
mounted(el) {
el.focus()
}
})app.directive('tooltip', {
mounted(el, { value }) {
const tip = document.createElement('div')
tip.className = 'tooltip'
tip.textContent = value
el._tooltip = tip
el.addEventListener('mouseenter', () => {
document.body.appendChild(tip)
const rect = el.getBoundingClientRect()
tip.style.top = rect.top - tip.offsetHeight - 8 + 'px'
tip.style.left = rect.left + 'px'
})
el.addEventListener('mouseleave', () => tip.remove())
},
updated(el, { value }) {
el._tooltip.textContent = value
},
unmounted(el) {
el._tooltip?.remove()
// важно: убрать event listeners
}
})app.directive('click-outside', {
mounted(el, { value }) {
el._clickOutside = (event) => {
if (!el.contains(event.target)) {
value(event) // value — callback функция
}
}
document.addEventListener('click', el._clickOutside)
},
unmounted(el) {
document.removeEventListener('click', el._clickOutside)
}
})<div v-click-outside="closeMenu">Меню</div><!-- Передаём объект, если нужно несколько параметров -->
<div v-tooltip="{ text: 'Подсказка', position: 'top', delay: 300 }">
Наведи на меня
</div>Система директив через data-атрибуты и MutationObserver — аналог того, как Vue применяет директивы к DOM
// Мини-система директив: регистрируем директивы по имени,
// применяем к элементам через data-атрибуты.
// MutationObserver следит за появлением новых элементов.
class DirectiveSystem {
constructor() {
this.directives = new Map()
this._observer = null
}
// Зарегистрировать директиву
directive(name, def) {
this.directives.set(name, def)
return this
}
// Применить директивы к элементу
applyToElement(el) {
for (const [name, def] of this.directives) {
const attrName = `data-v-${name}`
if (el.hasAttribute && el.hasAttribute(attrName)) {
const rawValue = el.getAttribute(attrName)
const binding = {
value: this._parseValue(rawValue),
arg: el.getAttribute(`data-v-${name}-arg`) || null,
modifiers: this._parseModifiers(el, name),
}
if (def.mounted) {
def.mounted(el, binding)
}
}
}
}
_parseValue(raw) {
if (!raw) return null
try { return JSON.parse(raw) } catch { return raw }
}
_parseModifiers(el, name) {
const mods = {}
const prefix = `data-v-${name}-mod-`
if (el.getAttributeNames) {
for (const attr of el.getAttributeNames()) {
if (attr.startsWith(prefix)) {
mods[attr.slice(prefix.length)] = true
}
}
}
return mods
}
// Обновить значение директивы
update(el, name, newValue) {
const def = this.directives.get(name)
if (!def || !def.updated) return
const binding = {
value: newValue,
oldValue: this._parseValue(el.getAttribute(`data-v-${name}`)),
arg: null,
modifiers: {},
}
el.setAttribute(`data-v-${name}`, JSON.stringify(newValue))
def.updated(el, binding)
}
// Отключить директиву
unmount(el, name) {
const def = this.directives.get(name)
if (def && def.unmounted) {
def.unmounted(el)
}
}
}
// --- Регистрируем директивы ---
const system = new DirectiveSystem()
// v-highlight: задаёт фоновый цвет
system.directive('highlight', {
mounted(el, binding) {
el.style = el.style || {}
el.style.backgroundColor = binding.value || '#yellow'
el.style.padding = '4px 8px'
el.style.borderRadius = '3px'
console.log(`[v-highlight] mounted: backgroundColor = ${binding.value}`)
},
updated(el, binding) {
el.style.backgroundColor = binding.value
console.log(`[v-highlight] updated: ${binding.oldValue} -> ${binding.value}`)
},
unmounted(el) {
el.style.backgroundColor = ''
console.log('[v-highlight] unmounted: стиль сброшен')
}
})
// v-badge: добавляет счётчик
system.directive('badge', {
mounted(el, binding) {
el._badge = `[${binding.value}]`
el.textContent = (el.textContent || '') + ' ' + el._badge
console.log(`[v-badge] mounted: badge = ${el._badge}`)
},
updated(el, binding) {
const oldBadge = `[${binding.oldValue}]`
const newBadge = `[${binding.value}]`
el.textContent = (el.textContent || '').replace(oldBadge, newBadge)
el._badge = newBadge
console.log(`[v-badge] updated: ${oldBadge} -> ${newBadge}`)
}
})
// --- Фейковый DOM-объект ---
const fakeEl = {
attributes: { 'data-v-highlight': '#3498db', 'data-v-badge': '5' },
style: {},
textContent: 'Кнопка',
hasAttribute(name) { return name in this.attributes },
getAttribute(name) { return this.attributes[name] ?? null },
getAttributeNames() { return Object.keys(this.attributes) },
setAttribute(name, val) { this.attributes[name] = val },
}
console.log('=== Применяем директивы ===')
system.applyToElement(fakeEl)
console.log('\n=== Обновляем директивы ===')
system.update(fakeEl, 'highlight', '#e74c3c')
system.update(fakeEl, 'badge', 12)
console.log('\n=== Размонтируем ===')
system.unmount(fakeEl, 'highlight')Директивы — специальные атрибуты с префиксом v-, дающие Vue-компилятору инструкции по управлению DOM. Встроенные директивы: v-if, v-for, v-model, v-show.
**Кастомные директивы** позволяют инкапсулировать логику работы с DOM-элементами в переиспользуемую единицу.
const app = createApp(App)
app.directive('focus', {
mounted(el) {
el.focus()
}
})<script setup>
// В <script setup> директивы начинающиеся с 'v' автоматически распознаются
const vFocus = {
mounted(el) { el.focus() }
}
</script>
<template>
<input v-focus />
</template>app.directive('my-directive', {
// Перед вставкой элемента в DOM
beforeMount(el, binding, vnode) {},
// После вставки элемента в DOM (самый частый)
mounted(el, binding, vnode) {},
// Перед обновлением компонента
beforeUpdate(el, binding, vnode, prevVnode) {},
// После обновления компонента и дочерних
updated(el, binding, vnode, prevVnode) {},
// Перед размонтированием
beforeUnmount(el, binding, vnode) {},
// После размонтирования
unmounted(el, binding, vnode) {},
})<!-- v-директива:аргумент.модификатор="значение" -->
<div v-highlight:background.animate="'#ff0000'"></div>app.directive('highlight', {
mounted(el, binding) {
console.log(binding.value) // '#ff0000' — переданное значение
console.log(binding.arg) // 'background' — аргумент
console.log(binding.modifiers) // { animate: true } — модификаторы
console.log(binding.oldValue) // предыдущее значение (в updated)
console.log(binding.instance) // экземпляр компонента
}
})app.directive('focus', {
mounted(el) {
el.focus()
}
})app.directive('tooltip', {
mounted(el, { value }) {
const tip = document.createElement('div')
tip.className = 'tooltip'
tip.textContent = value
el._tooltip = tip
el.addEventListener('mouseenter', () => {
document.body.appendChild(tip)
const rect = el.getBoundingClientRect()
tip.style.top = rect.top - tip.offsetHeight - 8 + 'px'
tip.style.left = rect.left + 'px'
})
el.addEventListener('mouseleave', () => tip.remove())
},
updated(el, { value }) {
el._tooltip.textContent = value
},
unmounted(el) {
el._tooltip?.remove()
// важно: убрать event listeners
}
})app.directive('click-outside', {
mounted(el, { value }) {
el._clickOutside = (event) => {
if (!el.contains(event.target)) {
value(event) // value — callback функция
}
}
document.addEventListener('click', el._clickOutside)
},
unmounted(el) {
document.removeEventListener('click', el._clickOutside)
}
})<div v-click-outside="closeMenu">Меню</div><!-- Передаём объект, если нужно несколько параметров -->
<div v-tooltip="{ text: 'Подсказка', position: 'top', delay: 300 }">
Наведи на меня
</div>Система директив через data-атрибуты и MutationObserver — аналог того, как Vue применяет директивы к DOM
// Мини-система директив: регистрируем директивы по имени,
// применяем к элементам через data-атрибуты.
// MutationObserver следит за появлением новых элементов.
class DirectiveSystem {
constructor() {
this.directives = new Map()
this._observer = null
}
// Зарегистрировать директиву
directive(name, def) {
this.directives.set(name, def)
return this
}
// Применить директивы к элементу
applyToElement(el) {
for (const [name, def] of this.directives) {
const attrName = `data-v-${name}`
if (el.hasAttribute && el.hasAttribute(attrName)) {
const rawValue = el.getAttribute(attrName)
const binding = {
value: this._parseValue(rawValue),
arg: el.getAttribute(`data-v-${name}-arg`) || null,
modifiers: this._parseModifiers(el, name),
}
if (def.mounted) {
def.mounted(el, binding)
}
}
}
}
_parseValue(raw) {
if (!raw) return null
try { return JSON.parse(raw) } catch { return raw }
}
_parseModifiers(el, name) {
const mods = {}
const prefix = `data-v-${name}-mod-`
if (el.getAttributeNames) {
for (const attr of el.getAttributeNames()) {
if (attr.startsWith(prefix)) {
mods[attr.slice(prefix.length)] = true
}
}
}
return mods
}
// Обновить значение директивы
update(el, name, newValue) {
const def = this.directives.get(name)
if (!def || !def.updated) return
const binding = {
value: newValue,
oldValue: this._parseValue(el.getAttribute(`data-v-${name}`)),
arg: null,
modifiers: {},
}
el.setAttribute(`data-v-${name}`, JSON.stringify(newValue))
def.updated(el, binding)
}
// Отключить директиву
unmount(el, name) {
const def = this.directives.get(name)
if (def && def.unmounted) {
def.unmounted(el)
}
}
}
// --- Регистрируем директивы ---
const system = new DirectiveSystem()
// v-highlight: задаёт фоновый цвет
system.directive('highlight', {
mounted(el, binding) {
el.style = el.style || {}
el.style.backgroundColor = binding.value || '#yellow'
el.style.padding = '4px 8px'
el.style.borderRadius = '3px'
console.log(`[v-highlight] mounted: backgroundColor = ${binding.value}`)
},
updated(el, binding) {
el.style.backgroundColor = binding.value
console.log(`[v-highlight] updated: ${binding.oldValue} -> ${binding.value}`)
},
unmounted(el) {
el.style.backgroundColor = ''
console.log('[v-highlight] unmounted: стиль сброшен')
}
})
// v-badge: добавляет счётчик
system.directive('badge', {
mounted(el, binding) {
el._badge = `[${binding.value}]`
el.textContent = (el.textContent || '') + ' ' + el._badge
console.log(`[v-badge] mounted: badge = ${el._badge}`)
},
updated(el, binding) {
const oldBadge = `[${binding.oldValue}]`
const newBadge = `[${binding.value}]`
el.textContent = (el.textContent || '').replace(oldBadge, newBadge)
el._badge = newBadge
console.log(`[v-badge] updated: ${oldBadge} -> ${newBadge}`)
}
})
// --- Фейковый DOM-объект ---
const fakeEl = {
attributes: { 'data-v-highlight': '#3498db', 'data-v-badge': '5' },
style: {},
textContent: 'Кнопка',
hasAttribute(name) { return name in this.attributes },
getAttribute(name) { return this.attributes[name] ?? null },
getAttributeNames() { return Object.keys(this.attributes) },
setAttribute(name, val) { this.attributes[name] = val },
}
console.log('=== Применяем директивы ===')
system.applyToElement(fakeEl)
console.log('\n=== Обновляем директивы ===')
system.update(fakeEl, 'highlight', '#e74c3c')
system.update(fakeEl, 'badge', 12)
console.log('\n=== Размонтируем ===')
system.unmount(fakeEl, 'highlight')Реализуй функцию `applyDirective(element, directiveName, directiveDef)` где `directiveDef = { mounted(el, binding) {}, updated(el, binding) {} }`. Реализуй директиву `vHighlight` — при `mounted` задаёт `el.style.backgroundColor = binding.value`, при `updated` обновляет цвет. Протестируй на фейковом DOM-объекте с полями `style` и `textContent`.
В applyDirective передавай binding = { value: ... } в mounted. Для метода update создай новый binding с обновлённым value и вызови updated. Убедись что el.style.backgroundColor присваивается внутри mounted и updated директивы vHighlight.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке