Custom directive changes
Understanding the Basic Concepts of Custom Directives
Vue.js custom directives allow developers to directly manipulate DOM elements. Unlike components, directives focus more on low-level DOM operations. A typical directive definition includes several hook functions:
Vue.directive('focus', {
bind(el, binding, vnode) {
// Called when the directive is first bound to the element
},
inserted(el, binding, vnode) {
// Called when the bound element is inserted into its parent node
},
update(el, binding, vnode, oldVnode) {
// Called when the component's VNode is updated
},
componentUpdated(el, binding, vnode, oldVnode) {
// Called after the component's VNode and its children VNodes have updated
},
unbind(el, binding, vnode) {
// Called when the directive is unbound from the element
}
})
Detailed Explanation of Directive Parameters
Each hook function receives the following parameters:
el
: The element the directive is bound tobinding
: An object containing multiple propertiesname
: The directive name (without thev-
prefix)value
: The value bound to the directiveoldValue
: The previous value bound to the directiveexpression
: The directive expression as a stringarg
: The argument passed to the directivemodifiers
: An object containing modifiers
Vue.directive('demo', {
bind(el, binding) {
console.log(binding.value) // => "hello"
console.log(binding.oldValue) // => undefined
console.log(binding.arg) // => "foo"
console.log(binding.modifiers) // => { bar: true }
}
})
// Usage example
<div v-demo:foo.bar="'hello'"></div>
Dynamic Directive Arguments
Directive arguments can be dynamic, which is particularly useful when you need the directive behavior to update reactively:
<div v-pin:[direction]="200">I will be pinned to the page</div>
Vue.directive('pin', {
bind(el, binding) {
el.style.position = 'fixed'
const direction = binding.arg || 'top'
el.style[direction] = binding.value + 'px'
},
update(el, binding) {
const direction = binding.arg || 'top'
el.style[direction] = binding.value + 'px'
}
})
new Vue({
data() {
return {
direction: 'left'
}
}
})
Reusing and Composing Directives
Custom directives can be combined to achieve complex functionality. For example, implementing a directive that combines permission checking and focus functionality:
Vue.directive('auth-focus', {
inserted(el, binding) {
const hasPermission = checkPermission(binding.value)
if (hasPermission) {
el.focus()
} else {
el.disabled = true
}
}
})
function checkPermission(permission) {
// In real projects, there would be more complex permission-checking logic here
return ['admin', 'editor'].includes(permission)
}
Directive and Component Communication
Directives can access component data and methods through the component instance:
Vue.directive('tooltip', {
bind(el, binding, vnode) {
const vm = vnode.context
el.addEventListener('mouseenter', () => {
vm.$refs.tooltip.show(binding.value)
})
el.addEventListener('mouseleave', () => {
vm.$refs.tooltip.hide()
})
}
})
Performance Optimization Techniques
DOM operations in directives may impact performance, so optimization is necessary:
Vue.directive('lazy-load', {
inserted(el, binding) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
el.src = binding.value
observer.unobserve(el)
}
})
})
observer.observe(el)
}
})
Directives and Vue 3's Composition API
In Vue 3, the way directives are defined has changed to better align with the Composition API style:
const app = createApp({})
app.directive('highlight', {
beforeMount(el, binding) {
// In Vue 3, 'bind' is renamed to 'beforeMount'
el.style.backgroundColor = binding.value
},
updated(el, binding) {
// Update logic
el.style.backgroundColor = binding.value
}
})
Complex Directive Example: Drag Directive
Implementing a complete drag-and-drop functionality directive:
Vue.directive('drag', {
inserted(el) {
el.style.cursor = 'move'
el.style.userSelect = 'none'
let startX, startY, initialLeft, initialTop
el.addEventListener('mousedown', startDrag)
function startDrag(e) {
e.preventDefault()
startX = e.clientX
startY = e.clientY
const styles = window.getComputedStyle(el)
initialLeft = parseInt(styles.left) || 0
initialTop = parseInt(styles.top) || 0
document.addEventListener('mousemove', drag)
document.addEventListener('mouseup', stopDrag)
}
function drag(e) {
const dx = e.clientX - startX
const dy = e.clientY - startY
el.style.left = `${initialLeft + dx}px`
el.style.top = `${initialTop + dy}px`
}
function stopDrag() {
document.removeEventListener('mousemove', drag)
document.removeEventListener('mouseup', stopDrag)
}
}
})
Directive Testing Strategy
Writing test cases for custom directives:
import { mount } from '@vue/test-utils'
import directive from '@/directives/highlight'
test('highlight directive applies color', () => {
const wrapper = mount({
template: '<div v-highlight="\'yellow\'"></div>'
}, {
global: {
directives: {
highlight: directive
}
}
})
expect(wrapper.element.style.backgroundColor).toBe('yellow')
})
Directives and TypeScript
Defining directives in TypeScript projects:
import { DirectiveBinding } from 'vue'
interface HighlightDirectiveBinding extends DirectiveBinding {
value: string
}
export default {
mounted(el: HTMLElement, binding: HighlightDirectiveBinding) {
el.style.backgroundColor = binding.value
},
updated(el: HTMLElement, binding: HighlightDirectiveBinding) {
el.style.backgroundColor = binding.value
}
}
Global vs. Local Directives
Comparing global and local registration:
// Global directive
Vue.directive('global-dir', {
// Options
})
// Local directive
const component = {
directives: {
'local-dir': {
// Options
}
},
template: '<div v-local-dir></div>'
}
Directive Lifecycle Changes
Comparison of directive lifecycles in Vue 2 and Vue 3:
Vue 2 Hook | Vue 3 Hook | Trigger Timing |
---|---|---|
bind | beforeMount | When the directive is first bound to the element |
inserted | mounted | When the element is inserted into its parent node |
update | updated | When the component updates |
componentUpdated | - | Removed in Vue 3 |
unbind | unmounted | When the directive is unbound from the element |
Best Practices for Custom Directives
- Maintain Single Responsibility: Each directive should do one thing only
- Consider Accessibility: Ensure directives don't interfere with screen readers and other assistive tools
- Provide Default Values: Offer reasonable defaults for optional parameters
- Handle Edge Cases: Consider the directive's behavior across different browsers and scenarios
- Document Directives: Write clear documentation for custom directives
/**
* v-debounce Debounce Directive
* @param {Function} value - The function to debounce
* @param {number} [delay=300] - Delay time in milliseconds
* @param {boolean} [immediate=false] - Whether to execute immediately
*/
Vue.directive('debounce', {
inserted(el, binding) {
const { value, modifiers } = binding
const delay = modifiers.delay || 300
const immediate = modifiers.immediate || false
let timer = null
el.addEventListener('input', () => {
if (timer) clearTimeout(timer)
if (immediate && !timer) {
value()
}
timer = setTimeout(() => {
if (!immediate) {
value()
}
timer = null
}, delay)
})
}
})
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:模板引用(ref)新特性
下一篇:动态组件API变更