阿里云主机折上折
  • 微信号
Current Site:Index > Custom directive changes

Custom directive changes

Author:Chuan Chen 阅读数:38303人阅读 分类: Vue.js

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 to
  • binding: An object containing multiple properties
    • name: The directive name (without the v- prefix)
    • value: The value bound to the directive
    • oldValue: The previous value bound to the directive
    • expression: The directive expression as a string
    • arg: The argument passed to the directive
    • modifiers: 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

  1. Maintain Single Responsibility: Each directive should do one thing only
  2. Consider Accessibility: Ensure directives don't interfere with screen readers and other assistive tools
  3. Provide Default Values: Offer reasonable defaults for optional parameters
  4. Handle Edge Cases: Consider the directive's behavior across different browsers and scenarios
  5. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.