最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue自定義指令生命周期鉤子的完全指南

 更新時(shí)間:2026年01月16日 08:56:54   作者:北辰alk  
Vue?自定義指令提供了強(qiáng)大的生命周期鉤子,讓你可以精準(zhǔn)控制指令在?DOM?元素上的行為,本文將深入解析所有鉤子函數(shù),并提供豐富的實(shí)用示例,感興趣的小伙伴可以了解下

一、指令生命周期概覽

1.1 生命周期鉤子總覽

const myDirective = {
  // 1. 綁定前(Vue 3 新增)
  beforeMount() {},
  
  // 2. 元素掛載時(shí)
  mounted() {},
  
  // 3. 更新前(Vue 2: bind → Vue 3: beforeUpdate)
  beforeUpdate() {},
  
  // 4. 更新后
  updated() {},
  
  // 5. 卸載前(Vue 2: unbind → Vue 3: beforeUnmount)
  beforeUnmount() {},
  
  // 6. 卸載后(Vue 2: unbind → Vue 3: unmounted)
  unmounted() {},
  
  // 7. Vue 2 特有
  bind() {},      // Vue 3 中被 beforeMount + mounted 替代
  inserted() {},  // Vue 3 中被 mounted 替代
  componentUpdated() {}, // Vue 3 中被 updated 替代
  unbind() {}     // Vue 3 中被 beforeUnmount + unmounted 替代
}

1.2 Vue 2 vs Vue 3 對(duì)比

// Vue 2 指令生命周期
const vue2Directive = {
  bind(el, binding, vnode, oldVnode) {
    // 只調(diào)用一次,指令第一次綁定到元素時(shí)調(diào)用
  },
  inserted(el, binding, vnode, oldVnode) {
    // 被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用
  },
  update(el, binding, vnode, oldVnode) {
    // 所在組件的 VNode 更新時(shí)調(diào)用
  },
  componentUpdated(el, binding, vnode, oldVnode) {
    // 指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用
  },
  unbind(el, binding, vnode, oldVnode) {
    // 只調(diào)用一次,指令與元素解綁時(shí)調(diào)用
  }
}

// Vue 3 指令生命周期(組合式API風(fēng)格)
const vue3Directive = {
  beforeMount(el, binding, vnode, prevVnode) {
    // 元素掛載前調(diào)用
  },
  mounted(el, binding, vnode, prevVnode) {
    // 元素掛載后調(diào)用
  },
  beforeUpdate(el, binding, vnode, prevVnode) {
    // 元素更新前調(diào)用
  },
  updated(el, binding, vnode, prevVnode) {
    // 元素更新后調(diào)用
  },
  beforeUnmount(el, binding, vnode, prevVnode) {
    // 元素卸載前調(diào)用
  },
  unmounted(el, binding, vnode, prevVnode) {
    // 元素卸載后調(diào)用
  }
}

二、指令鉤子函數(shù)詳解

2.1 鉤子參數(shù)解析

const directive = {
  mounted(el, binding, vnode, prevVnode) {
    // 參數(shù)詳解:
    
    // 1. el - 指令綁定的元素
    console.log('元素:', el)  // DOM 元素
    
    // 2. binding - 包含指令信息的對(duì)象
    console.log('binding 對(duì)象:', {
      // 指令的值(v-my-directive="value" 中的 value)
      value: binding.value,
      
      // 舊值(僅在 beforeUpdate 和 updated 中可用)
      oldValue: binding.oldValue,
      
      // 參數(shù)(v-my-directive:arg 中的 arg)
      arg: binding.arg,
      
      // 修飾符對(duì)象(v-my-directive.modifier 中的 modifier)
      modifiers: binding.modifiers,
      
      // 指令的實(shí)例
      instance: binding.instance,
      
      // 指令的定義對(duì)象
      dir: binding.dir
    })
    
    // 3. vnode - 綁定元素的虛擬節(jié)點(diǎn)
    console.log('虛擬節(jié)點(diǎn):', {
      type: vnode.type,
      props: vnode.props,
      children: vnode.children,
      el: vnode.el,  // 對(duì)應(yīng)的 DOM 元素
      component: vnode.component,  // 組件實(shí)例
      dirs: vnode.dirs  // 指令數(shù)組
    })
    
    // 4. prevVnode - 先前的虛擬節(jié)點(diǎn)(僅在更新鉤子中可用)
    console.log('先前虛擬節(jié)點(diǎn):', prevVnode)
  }
}

2.2 鉤子執(zhí)行時(shí)機(jī)詳解

<template>
  <div>
    <!-- 指令生命周期演示 -->
    <div 
      v-lifecycle-demo="counter" 
      v-if="showElement"
      :class="{ active: isActive }"
    >
      指令生命周期演示
    </div>
    
    <button @click="increment">增加: {{ counter }}</button>
    <button @click="toggleElement">切換顯示</button>
    <button @click="toggleClass">切換類(lèi)名</button>
  </div>
</template>

<script>
// 生命周期演示指令
const lifecycleDemo = {
  beforeMount(el, binding) {
    console.log('1. beforeMount - 綁定前', {
      value: binding.value,
      elementExists: !!el.parentNode
    })
  },
  
  mounted(el, binding, vnode) {
    console.log('2. mounted - 掛載完成', {
      value: binding.value,
      elementInDOM: document.body.contains(el),
      parent: el.parentNode?.tagName
    })
    
    // 添加初始樣式
    el.style.transition = 'all 0.3s ease'
  },
  
  beforeUpdate(el, binding) {
    console.log('3. beforeUpdate - 更新前', {
      oldValue: binding.oldValue,
      newValue: binding.value,
      willUpdate: binding.value !== binding.oldValue
    })
  },
  
  updated(el, binding) {
    console.log('4. updated - 更新完成', {
      oldValue: binding.oldValue,
      newValue: binding.value,
      elementText: el.textContent
    })
    
    // 根據(jù)值變化添加動(dòng)畫(huà)
    if (binding.value > binding.oldValue) {
      el.style.transform = 'scale(1.1)'
      setTimeout(() => {
        el.style.transform = 'scale(1)'
      }, 300)
    }
  },
  
  beforeUnmount(el, binding) {
    console.log('5. beforeUnmount - 卸載前', {
      value: binding.value,
      elementInDOM: document.body.contains(el)
    })
    
    // 添加淡出動(dòng)畫(huà)
    el.style.opacity = '0.5'
  },
  
  unmounted(el, binding) {
    console.log('6. unmounted - 卸載完成', {
      value: binding.value,
      elementInDOM: false,  // 此時(shí)元素已從DOM移除
      elementReference: el  // el 仍然可以訪(fǎng)問(wèn),但已不在DOM中
    })
  }
}

export default {
  directives: {
    'lifecycle-demo': lifecycleDemo
  },
  data() {
    return {
      counter: 0,
      showElement: true,
      isActive: false
    }
  },
  methods: {
    increment() {
      this.counter++
    },
    toggleElement() {
      this.showElement = !this.showElement
    },
    toggleClass() {
      this.isActive = !this.isActive
    }
  },
  mounted() {
    console.log('組件 mounted - 開(kāi)始觀察指令生命周期')
  }
}
</script>

三、實(shí)用指令示例

3.1 焦點(diǎn)管理指令

// 自動(dòng)聚焦指令
const vFocus = {
  mounted(el, binding) {
    const { value = true, arg = 'auto', modifiers } = binding
    
    if (value) {
      // 立即聚焦
      if (arg === 'immediate') {
        el.focus()
      }
      // 延遲聚焦
      else if (arg === 'delay') {
        setTimeout(() => {
          el.focus()
        }, binding.value.delay || 100)
      }
      // 條件聚焦
      else if (arg === 'conditional') {
        if (binding.value.condition) {
          el.focus()
        }
      }
      // 自動(dòng)聚焦(默認(rèn))
      else {
        // 對(duì)于輸入框,自動(dòng)聚焦
        if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
          el.focus()
        }
      }
      
      // 處理修飾符
      if (modifiers.select) {
        el.select()
      }
      
      if (modifiers.end) {
        el.setSelectionRange(el.value.length, el.value.length)
      }
    }
  },
  
  updated(el, binding) {
    // 值變化時(shí)重新聚焦
    if (binding.value !== binding.oldValue && binding.value) {
      el.focus()
      
      if (binding.modifiers.select) {
        el.select()
      }
    }
  },
  
  beforeUnmount(el) {
    // 卸載前移除焦點(diǎn)
    el.blur()
  }
}

// 用法示例
// <input v-focus>                    // 自動(dòng)聚焦
// <input v-focus.immediate>         // 立即聚焦
// <input v-focus:delay="{delay: 500}"> // 延遲500ms聚焦
// <input v-focus:conditional="{condition: shouldFocus}"> // 條件聚焦
// <input v-focus.select>            // 聚焦并選中文本
// <input v-focus.end>               // 聚焦到末尾

3.2 點(diǎn)擊外部指令

// 點(diǎn)擊外部關(guān)閉指令
const vClickOutside = {
  beforeMount(el, binding) {
    // 創(chuàng)建事件處理函數(shù)
    el._clickOutsideHandler = function(event) {
      // 檢查點(diǎn)擊是否在元素外部
      if (!(el === event.target || el.contains(event.target))) {
        // 調(diào)用綁定函數(shù)
        binding.value(event)
      }
    }
    
    // 添加事件監(jiān)聽(tīng)
    document.addEventListener('click', el._clickOutsideHandler)
    
    // 可選:添加其他事件類(lèi)型
    if (binding.modifiers.mousedown) {
      document.addEventListener('mousedown', el._clickOutsideHandler)
    }
    
    if (binding.modifiers.touchstart) {
      document.addEventListener('touchstart', el._clickOutsideHandler)
    }
  },
  
  updated(el, binding) {
    // 更新時(shí)檢查值是否變化
    if (binding.value !== binding.oldValue) {
      // 可以在這里更新處理邏輯
      console.log('點(diǎn)擊外部指令值已更新')
    }
  },
  
  unmounted(el, binding) {
    // 移除事件監(jiān)聽(tīng)
    document.removeEventListener('click', el._clickOutsideHandler)
    
    if (binding.modifiers.mousedown) {
      document.removeEventListener('mousedown', el._clickOutsideHandler)
    }
    
    if (binding.modifiers.touchstart) {
      document.removeEventListener('touchstart', el._clickOutsideHandler)
    }
    
    // 清理引用
    delete el._clickOutsideHandler
  }
}

// 高級(jí)版本:支持配置和動(dòng)態(tài)啟用/禁用
const vClickOutsideAdvanced = {
  mounted(el, binding) {
    const { value, modifiers, arg } = binding
    
    // 默認(rèn)配置
    const defaultConfig = {
      handler: value,
      events: ['click'],
      enabled: true,
      capture: false,
      immediate: false
    }
    
    // 合并配置
    const config = typeof value === 'function' 
      ? { ...defaultConfig, handler: value }
      : { ...defaultConfig, ...value }
    
    // 處理修飾符
    if (modifiers.mousedown) config.events.push('mousedown')
    if (modifiers.touchstart) config.events.push('touchstart')
    if (modifiers.capture) config.capture = true
    if (modifiers.immediate) config.immediate = true
    
    // 處理參數(shù)
    if (arg === 'except') {
      // 排除某些元素
      config.except = binding.value?.except || []
    }
    
    // 存儲(chǔ)配置
    el._clickOutsideConfig = config
    
    // 事件處理函數(shù)
    el._clickOutsideHandler = (event) => {
      if (!config.enabled) return
      
      // 檢查點(diǎn)擊是否在排除列表中
      if (config.except) {
        const clickedInsideExcept = config.except.some(selector => {
          const elements = document.querySelectorAll(selector)
          return Array.from(elements).some(element => 
            element.contains(event.target)
          )
        })
        
        if (clickedInsideExcept) return
      }
      
      // 檢查點(diǎn)擊是否在元素外部
      if (!(el === event.target || el.contains(event.target))) {
        config.handler(event, el)
      }
    }
    
    // 添加事件監(jiān)聽(tīng)
    config.events.forEach(eventName => {
      document.addEventListener(
        eventName, 
        el._clickOutsideHandler, 
        config.capture
      )
    })
    
    // 立即執(zhí)行一次檢查(如果配置了)
    if (config.immediate) {
      // 模擬外部點(diǎn)擊
      setTimeout(() => {
        const fakeEvent = new MouseEvent('click')
        el._clickOutsideHandler(fakeEvent)
      })
    }
  },
  
  updated(el, binding) {
    const oldConfig = el._clickOutsideConfig
    const newValue = binding.value
    
    // 更新配置
    if (typeof newValue === 'function') {
      el._clickOutsideConfig.handler = newValue
    } else if (typeof newValue === 'object') {
      Object.assign(el._clickOutsideConfig, newValue)
    }
    
    // 如果啟停狀態(tài)變化,可能需要更新事件監(jiān)聽(tīng)
    if (oldConfig?.enabled !== el._clickOutsideConfig?.enabled) {
      console.log('點(diǎn)擊外部指令啟停狀態(tài)變化')
    }
  },
  
  beforeUnmount(el) {
    const config = el._clickOutsideConfig
    
    if (config && el._clickOutsideHandler) {
      // 移除所有事件監(jiān)聽(tīng)
      config.events.forEach(eventName => {
        document.removeEventListener(
          eventName, 
          el._clickOutsideHandler, 
          config.capture
        )
      })
      
      // 清理引用
      delete el._clickOutsideConfig
      delete el._clickOutsideHandler
    }
  }
}

// 用法示例
// <div v-click-outside="closeMenu">菜單內(nèi)容</div>
// <div v-click-outside.advanced="{ handler: closeMenu, enabled: isOpen }">
// <div v-click-outside:except="{ handler: closeMenu, except: ['.ignore-click'] }">
// <div v-click-outside.mousedown.touchstart="closeMenu">

3.3 滾動(dòng)指令

// 滾動(dòng)指令集
const scrollDirectives = {
  // 無(wú)限滾動(dòng)指令
  infiniteScroll: {
    mounted(el, binding) {
      const { value: callback, modifiers, arg } = binding
      
      // 配置選項(xiàng)
      const options = {
        distance: 50,           // 觸發(fā)距離
        delay: 100,            // 防抖延遲
        immediate: true,       // 立即檢查
        disabled: false,       // 是否禁用
        direction: 'vertical'  // 滾動(dòng)方向
      }
      
      // 解析參數(shù)
      if (arg === 'distance') {
        options.distance = parseInt(binding.value) || options.distance
      } else if (typeof binding.value === 'object') {
        Object.assign(options, binding.value)
      }
      
      // 存儲(chǔ)配置
      el._infiniteScrollOptions = options
      
      // 防抖函數(shù)
      let checkTimer = null
      const checkScroll = () => {
        if (options.disabled) return
        
        let isAtEnd = false
        
        if (options.direction === 'vertical') {
          const scrollTop = el.scrollTop
          const scrollHeight = el.scrollHeight
          const clientHeight = el.clientHeight
          
          isAtEnd = scrollHeight - scrollTop - clientHeight <= options.distance
        } else {
          const scrollLeft = el.scrollLeft
          const scrollWidth = el.scrollWidth
          const clientWidth = el.clientWidth
          
          isAtEnd = scrollWidth - scrollLeft - clientWidth <= options.distance
        }
        
        if (isAtEnd) {
          callback()
        }
      }
      
      const debouncedCheck = () => {
        if (checkTimer) clearTimeout(checkTimer)
        checkTimer = setTimeout(checkScroll, options.delay)
      }
      
      // 監(jiān)聽(tīng)滾動(dòng)事件
      el._infiniteScrollHandler = debouncedCheck
      el.addEventListener('scroll', el._infiniteScrollHandler)
      
      // 監(jiān)聽(tīng)窗口大小變化
      el._resizeHandler = debouncedCheck
      window.addEventListener('resize', el._resizeHandler)
      
      // 立即檢查一次
      if (options.immediate) {
        setTimeout(checkScroll, 100)
      }
    },
    
    updated(el, binding) {
      const options = el._infiniteScrollOptions
      const newValue = binding.value
      
      // 更新配置
      if (typeof newValue === 'object') {
        Object.assign(options, newValue)
      } else if (typeof newValue === 'function') {
        // 如果傳入了新函數(shù),需要更新回調(diào)
        // 注意:這里需要重新綁定事件,簡(jiǎn)化處理
      }
      
      // 強(qiáng)制檢查一次
      if (binding.modifiers.force) {
        setTimeout(() => {
          const event = new Event('scroll')
          el.dispatchEvent(event)
        })
      }
    },
    
    beforeUnmount(el) {
      // 清理事件監(jiān)聽(tīng)
      if (el._infiniteScrollHandler) {
        el.removeEventListener('scroll', el._infiniteScrollHandler)
        delete el._infiniteScrollHandler
      }
      
      if (el._resizeHandler) {
        window.removeEventListener('resize', el._resizeHandler)
        delete el._resizeHandler
      }
      
      delete el._infiniteScrollOptions
    }
  },
  
  // 滾動(dòng)到元素指令
  scrollTo: {
    mounted(el, binding) {
      const { value, modifiers, arg } = binding
      
      el._scrollToHandler = (event) => {
        event.preventDefault()
        
        const targetSelector = typeof value === 'string' ? value : value?.target
        const options = typeof value === 'object' ? value : {}
        
        const targetElement = targetSelector 
          ? document.querySelector(targetSelector)
          : document.documentElement
        
        if (!targetElement) return
        
        // 滾動(dòng)配置
        const scrollOptions = {
          behavior: modifiers.smooth ? 'smooth' : 'auto',
          block: arg || 'start',  // start, center, end, nearest
          inline: 'nearest'
        }
        
        // 合并選項(xiàng)
        Object.assign(scrollOptions, options)
        
        // 執(zhí)行滾動(dòng)
        targetElement.scrollIntoView(scrollOptions)
        
        // 觸發(fā)回調(diào)
        if (typeof value === 'object' && value.onScroll) {
          value.onScroll(targetElement)
        }
      }
      
      // 添加點(diǎn)擊事件
      el.addEventListener('click', el._scrollToHandler)
      
      // 自動(dòng)滾動(dòng)(如果配置了)
      if (modifiers.auto) {
        setTimeout(() => {
          el._scrollToHandler(new Event('click'))
        }, value?.delay || 0)
      }
    },
    
    updated(el, binding) {
      // 如果值變化且配置了auto,重新觸發(fā)
      if (binding.value !== binding.oldValue && binding.modifiers.auto) {
        setTimeout(() => {
          if (el._scrollToHandler) {
            el._scrollToHandler(new Event('click'))
          }
        }, binding.value?.delay || 0)
      }
    },
    
    beforeUnmount(el) {
      if (el._scrollToHandler) {
        el.removeEventListener('click', el._scrollToHandler)
        delete el._scrollToHandler
      }
    }
  }
}

// 用法示例
// <div v-infinite-scroll="loadMore">內(nèi)容...</div>
// <div v-infinite-scroll.distance="100">自定義距離</div>
// <div v-infinite-scroll="{ handler: loadMore, distance: 100, disabled: isLoading }">
// <button v-scroll-to="'#section'">滾動(dòng)到章節(jié)</button>
// <button v-scroll-to.smooth.auto="{ target: '#section', delay: 500 }">自動(dòng)滾動(dòng)</button>

3.4 拖放指令

// 拖放指令
const vDrag = {
  beforeMount(el, binding) {
    const { value, modifiers } = binding
    
    // 默認(rèn)配置
    const config = {
      data: null,           // 拖拽數(shù)據(jù)
      effect: 'move',       // 拖拽效果
      disabled: false,      // 是否禁用
      handle: null,         // 拖拽手柄選擇器
      ghost: true,          // 顯示幽靈圖像
      clone: false,         // 克隆元素
      axis: 'both',         // 拖拽軸向:both, x, y
      boundary: null,       // 邊界限制
      onStart: null,        // 開(kāi)始回調(diào)
      onMove: null,         // 移動(dòng)回調(diào)
      onEnd: null           // 結(jié)束回調(diào)
    }
    
    // 合并配置
    if (typeof value === 'object') {
      Object.assign(config, value)
    } else if (value !== undefined) {
      config.data = value
    }
    
    // 處理修飾符
    if (modifiers.copy) config.effect = 'copy'
    if (modifiers.link) config.effect = 'link'
    if (modifiers.x) config.axis = 'x'
    if (modifiers.y) config.axis = 'y'
    if (modifiers.noGhost) config.ghost = false
    if (modifiers.clone) config.clone = true
    
    // 存儲(chǔ)配置和狀態(tài)
    el._dragConfig = config
    el._dragState = {
      isDragging: false,
      startX: 0,
      startY: 0,
      offsetX: 0,
      offsetY: 0,
      clone: null
    }
    
    // 設(shè)置元素屬性
    el.setAttribute('draggable', !config.disabled)
    
    // 找到拖拽手柄
    const dragHandle = config.handle 
      ? el.querySelector(config.handle) 
      : el
    
    // 事件處理函數(shù)
    const onDragStart = (e) => {
      if (config.disabled) {
        e.preventDefault()
        return
      }
      
      el._dragState.isDragging = true
      el._dragState.startX = e.clientX
      el._dragState.startY = e.clientY
      
      // 設(shè)置拖拽數(shù)據(jù)
      if (config.data !== null) {
        const dataString = typeof config.data === 'string' 
          ? config.data 
          : JSON.stringify(config.data)
        
        e.dataTransfer.setData('application/json', dataString)
        e.dataTransfer.setData('text/plain', dataString)
      }
      
      // 設(shè)置拖拽效果
      e.dataTransfer.effectAllowed = config.effect
      
      // 創(chuàng)建幽靈圖像
      if (config.ghost) {
        const ghost = el.cloneNode(true)
        ghost.style.opacity = '0.5'
        ghost.style.position = 'absolute'
        ghost.style.top = '-1000px'
        document.body.appendChild(ghost)
        e.dataTransfer.setDragImage(ghost, 0, 0)
        
        // 稍后移除
        setTimeout(() => {
          if (document.body.contains(ghost)) {
            document.body.removeChild(ghost)
          }
        }, 0)
      }
      
      // 克隆元素(如果需要)
      if (config.clone) {
        const clone = el.cloneNode(true)
        clone.style.position = 'absolute'
        clone.style.zIndex = '1000'
        clone.style.pointerEvents = 'none'
        clone.style.opacity = '0.7'
        document.body.appendChild(clone)
        el._dragState.clone = clone
      }
      
      // 觸發(fā)開(kāi)始回調(diào)
      if (typeof config.onStart === 'function') {
        config.onStart(e, el, config.data)
      }
      
      // 添加拖拽樣式
      el.classList.add('dragging')
    }
    
    const onDrag = (e) => {
      if (!el._dragState.isDragging) return
      
      // 計(jì)算偏移
      const deltaX = e.clientX - el._dragState.startX
      const deltaY = e.clientY - el._dragState.startY
      
      // 軸向限制
      if (config.axis === 'x') {
        el._dragState.offsetX = deltaX
        el._dragState.offsetY = 0
      } else if (config.axis === 'y') {
        el._dragState.offsetX = 0
        el._dragState.offsetY = deltaY
      } else {
        el._dragState.offsetX = deltaX
        el._dragState.offsetY = deltaY
      }
      
      // 邊界限制
      if (config.boundary) {
        const boundary = typeof config.boundary === 'string'
          ? document.querySelector(config.boundary)
          : config.boundary
        
        if (boundary) {
          const boundRect = boundary.getBoundingClientRect()
          const elRect = el.getBoundingClientRect()
          
          // 限制在邊界內(nèi)
          el._dragState.offsetX = Math.max(
            boundRect.left - elRect.left,
            Math.min(el._dragState.offsetX, boundRect.right - elRect.right)
          )
          
          el._dragState.offsetY = Math.max(
            boundRect.top - elRect.top,
            Math.min(el._dragState.offsetY, boundRect.bottom - elRect.bottom)
          )
        }
      }
      
      // 更新克隆元素位置
      if (el._dragState.clone) {
        el._dragState.clone.style.transform = 
          `translate(${el._dragState.offsetX}px, ${el._dragState.offsetY}px)`
      }
      
      // 觸發(fā)移動(dòng)回調(diào)
      if (typeof config.onMove === 'function') {
        config.onMove(e, el, {
          offsetX: el._dragState.offsetX,
          offsetY: el._dragState.offsetY,
          deltaX,
          deltaY
        })
      }
    }
    
    const onDragEnd = (e) => {
      if (!el._dragState.isDragging) return
      
      el._dragState.isDragging = false
      
      // 移除克隆元素
      if (el._dragState.clone) {
        document.body.removeChild(el._dragState.clone)
        el._dragState.clone = null
      }
      
      // 觸發(fā)結(jié)束回調(diào)
      if (typeof config.onEnd === 'function') {
        config.onEnd(e, el, {
          offsetX: el._dragState.offsetX,
          offsetY: el._dragState.offsetY,
          success: e.dataTransfer.dropEffect !== 'none'
        })
      }
      
      // 重置狀態(tài)
      el._dragState.offsetX = 0
      el._dragState.offsetY = 0
      
      // 移除拖拽樣式
      el.classList.remove('dragging')
    }
    
    // 綁定事件
    dragHandle.addEventListener('dragstart', onDragStart)
    dragHandle.addEventListener('drag', onDrag)
    dragHandle.addEventListener('dragend', onDragEnd)
    
    // 存儲(chǔ)事件處理函數(shù)以便清理
    el._dragHandlers = { onDragStart, onDrag, onDragEnd }
  },
  
  updated(el, binding) {
    const config = el._dragConfig
    const newValue = binding.value
    
    // 更新配置
    if (typeof newValue === 'object') {
      Object.assign(config, newValue)
    }
    
    // 更新 draggable 屬性
    el.setAttribute('draggable', !config.disabled)
    
    // 如果禁用狀態(tài)變化
    if (binding.oldValue?.disabled !== config.disabled) {
      console.log('拖拽指令禁用狀態(tài)變化:', config.disabled)
    }
  },
  
  beforeUnmount(el) {
    const dragHandle = el._dragConfig?.handle 
      ? el.querySelector(el._dragConfig.handle) 
      : el
    
    const handlers = el._dragHandlers
    
    if (dragHandle && handlers) {
      dragHandle.removeEventListener('dragstart', handlers.onDragStart)
      dragHandle.removeEventListener('drag', handlers.onDrag)
      dragHandle.removeEventListener('dragend', handlers.onDragEnd)
    }
    
    // 清理克隆元素
    if (el._dragState?.clone && document.body.contains(el._dragState.clone)) {
      document.body.removeChild(el._dragState.clone)
    }
    
    // 清理引用
    delete el._dragConfig
    delete el._dragState
    delete el._dragHandlers
  }
}

// 用法示例
// <div v-drag="dragData">可拖拽</div>
// <div v-drag.copy>復(fù)制模式</div>
// <div v-drag.x>僅水平拖拽</div>
// <div v-drag="{ data: item, disabled: !isEditable, onEnd: handleDrop }">
// <div v-drag.clone>拖拽時(shí)顯示克隆</div>
// <div v-drag.no-ghost>不顯示幽靈圖像</div>

3.5 權(quán)限控制指令

// 權(quán)限控制指令集
const permissionDirectives = {
  // 角色權(quán)限指令
  role: {
    beforeMount(el, binding) {
      const { value, modifiers, arg } = binding
      
      // 獲取當(dāng)前用戶(hù)角色
      const userRole = getCurrentUserRole()
      
      // 檢查權(quán)限
      const hasPermission = checkRolePermission(userRole, value, arg)
      
      // 根據(jù)權(quán)限顯示/隱藏元素
      if (!hasPermission) {
        if (modifiers.hide) {
          // 隱藏元素
          el.style.display = 'none'
          el._originalDisplay = el.style.display
        } else if (modifiers.disable) {
          // 禁用元素
          el.disabled = true
          el._originalDisabled = el.disabled
          el.classList.add('disabled')
        } else if (modifiers.remove) {
          // 移除元素
          el.parentNode?.removeChild(el)
        } else {
          // 默認(rèn):隱藏元素
          el.style.display = 'none'
          el._originalDisplay = el.style.display
        }
      }
      
      // 存儲(chǔ)權(quán)限信息
      el._permissionInfo = {
        required: value,
        userRole,
        hasPermission,
        action: arg || 'view'
      }
    },
    
    updated(el, binding) {
      const oldPermission = el._permissionInfo
      const newValue = binding.value
      
      // 重新檢查權(quán)限
      const userRole = getCurrentUserRole()
      const hasPermission = checkRolePermission(userRole, newValue, binding.arg)
      
      // 如果權(quán)限狀態(tài)變化
      if (oldPermission.hasPermission !== hasPermission) {
        // 恢復(fù)原始狀態(tài)
        if (oldPermission.hasPermission === false) {
          if (binding.modifiers.hide && el._originalDisplay !== undefined) {
            el.style.display = el._originalDisplay
          } else if (binding.modifiers.disable && el._originalDisabled !== undefined) {
            el.disabled = el._originalDisabled
            el.classList.remove('disabled')
          }
        }
        
        // 應(yīng)用新權(quán)限
        if (!hasPermission) {
          if (binding.modifiers.hide) {
            el._originalDisplay = el.style.display
            el.style.display = 'none'
          } else if (binding.modifiers.disable) {
            el._originalDisabled = el.disabled
            el.disabled = true
            el.classList.add('disabled')
          } else if (binding.modifiers.remove) {
            el.parentNode?.removeChild(el)
          }
        }
        
        // 更新權(quán)限信息
        el._permissionInfo = {
          required: newValue,
          userRole,
          hasPermission,
          action: binding.arg || 'view'
        }
      }
    },
    
    unmounted(el) {
      // 清理
      delete el._permissionInfo
      delete el._originalDisplay
      delete el._originalDisabled
    }
  },
  
  // 功能權(quán)限指令
  feature: {
    mounted(el, binding) {
      const { value, modifiers } = binding
      
      // 檢查功能是否啟用
      const isEnabled = checkFeatureEnabled(value)
      
      if (!isEnabled) {
        if (modifiers.hide) {
          el.style.display = 'none'
        } else if (modifiers.disable) {
          el.disabled = true
          el.classList.add('disabled')
        } else {
          el.style.opacity = '0.5'
          el.style.pointerEvents = 'none'
        }
      }
      
      el._featureInfo = {
        feature: value,
        enabled: isEnabled
      }
    },
    
    updated(el, binding) {
      const isEnabled = checkFeatureEnabled(binding.value)
      
      if (el._featureInfo.enabled !== isEnabled) {
        if (isEnabled) {
          // 恢復(fù)
          if (binding.modifiers.hide) {
            el.style.display = ''
          } else if (binding.modifiers.disable) {
            el.disabled = false
            el.classList.remove('disabled')
          } else {
            el.style.opacity = ''
            el.style.pointerEvents = ''
          }
        } else {
          // 禁用
          if (binding.modifiers.hide) {
            el.style.display = 'none'
          } else if (binding.modifiers.disable) {
            el.disabled = true
            el.classList.add('disabled')
          } else {
            el.style.opacity = '0.5'
            el.style.pointerEvents = 'none'
          }
        }
        
        el._featureInfo = {
          feature: binding.value,
          enabled: isEnabled
        }
      }
    },
    
    unmounted(el) {
      delete el._featureInfo
    }
  }
}

// 工具函數(shù)
function getCurrentUserRole() {
  // 從Vuex、Pinia或localStorage獲取
  return localStorage.getItem('userRole') || 'guest'
}

function checkRolePermission(userRole, required, action = 'view') {
  // 權(quán)限配置
  const permissions = {
    admin: ['create', 'read', 'update', 'delete', 'manage'],
    editor: ['create', 'read', 'update'],
    viewer: ['read'],
    guest: []
  }
  
  // 如果是數(shù)組,檢查任意一個(gè)
  if (Array.isArray(required)) {
    return required.some(role => 
      permissions[userRole]?.includes(action) && role === userRole
    )
  }
  
  // 如果是字符串,精確匹配
  return permissions[userRole]?.includes(action) && required === userRole
}

function checkFeatureEnabled(feature) {
  // 從配置或特性開(kāi)關(guān)獲取
  const featureFlags = JSON.parse(localStorage.getItem('featureFlags') || '{}')
  return featureFlags[feature] !== false
}

// 用法示例
// <button v-role="'admin'">僅管理員可見(jiàn)</button>
// <button v-role="['admin', 'editor']">管理員和編輯可見(jiàn)</button>
// <button v-role:edit="'admin'">管理員可編輯</button>
// <button v-role.hide="'admin'">非管理員隱藏</button>
// <button v-role.disable="'admin'">非管理員禁用</button>
// <div v-feature="'newUI'">新UI功能</div>
// <div v-feature.disable="'betaFeature'">測(cè)試功能</div>

四、指令組合與復(fù)用

4.1 指令組合器

// 指令組合器:將多個(gè)指令組合成一個(gè)
function createDirectiveComposer(...directives) {
  return {
    beforeMount(...args) {
      directives.forEach(directive => {
        if (directive.beforeMount) directive.beforeMount(...args)
      })
    },
    
    mounted(...args) {
      directives.forEach(directive => {
        if (directive.mounted) directive.mounted(...args)
      })
    },
    
    beforeUpdate(...args) {
      directives.forEach(directive => {
        if (directive.beforeUpdate) directive.beforeUpdate(...args)
      })
    },
    
    updated(...args) {
      directives.forEach(directive => {
        if (directive.updated) directive.updated(...args)
      })
    },
    
    beforeUnmount(...args) {
      directives.forEach(directive => {
        if (directive.beforeUnmount) directive.beforeUnmount(...args)
      })
    },
    
    unmounted(...args) {
      directives.forEach(directive => {
        if (directive.unmounted) directive.unmounted(...args)
      })
    }
  }
}

// 使用示例
const vTooltip = {
  mounted(el, binding) {
    el.title = binding.value
    el.classList.add('has-tooltip')
  },
  unmounted(el) {
    el.classList.remove('has-tooltip')
  }
}

const vHighlight = {
  mounted(el, binding) {
    if (binding.value) {
      el.classList.add('highlight')
    }
  },
  updated(el, binding) {
    if (binding.value) {
      el.classList.add('highlight')
    } else {
      el.classList.remove('highlight')
    }
  }
}

// 組合指令
const vTooltipHighlight = createDirectiveComposer(vTooltip, vHighlight)

// 注冊(cè)
app.directive('tooltip-highlight', vTooltipHighlight)

// 使用
// <div v-tooltip-highlight="'提示文本'">內(nèi)容</div>

4.2 指令工廠函數(shù)

// 指令工廠:創(chuàng)建可配置的指令
function createResizableDirective(options = {}) {
  const defaultOptions = {
    handles: ['right', 'bottom', 'bottom-right'],
    minWidth: 100,
    minHeight: 100,
    maxWidth: null,
    maxHeight: null,
    onResize: null,
    onResizeStart: null,
    onResizeEnd: null
  }
  
  const config = { ...defaultOptions, ...options }
  
  return {
    mounted(el, binding) {
      const instanceOptions = typeof binding.value === 'object' 
        ? { ...config, ...binding.value }
        : config
      
      // 創(chuàng)建調(diào)整大小的手柄
      const handles = instanceOptions.handles
      const handleElements = []
      
      handles.forEach(handle => {
        const handleEl = document.createElement('div')
        handleEl.className = `resize-handle resize-handle-${handle}`
        handleEl.dataset.handle = handle
        
        // 添加事件監(jiān)聽(tīng)
        handleEl.addEventListener('mousedown', (e) => {
          e.preventDefault()
          e.stopPropagation()
          startResize(e, handle, el, instanceOptions)
        })
        
        el.appendChild(handleEl)
        handleElements.push(handleEl)
      })
      
      // 存儲(chǔ)引用
      el._resizeHandles = handleElements
      el._resizeOptions = instanceOptions
      
      // 添加可調(diào)整大小的樣式
      el.classList.add('resizable')
    },
    
    updated(el, binding) {
      // 更新選項(xiàng)
      if (typeof binding.value === 'object') {
        Object.assign(el._resizeOptions, binding.value)
      }
    },
    
    beforeUnmount(el) {
      // 清理手柄
      if (el._resizeHandles) {
        el._resizeHandles.forEach(handle => {
          handle.removeEventListener('mousedown', handle._resizeHandler)
          el.removeChild(handle)
        })
        delete el._resizeHandles
      }
      
      delete el._resizeOptions
      el.classList.remove('resizable')
    }
  }
}

// 調(diào)整大小邏輯
function startResize(e, handle, el, options) {
  const startX = e.clientX
  const startY = e.clientY
  const startWidth = el.offsetWidth
  const startHeight = el.offsetHeight
  
  // 觸發(fā)開(kāi)始回調(diào)
  if (typeof options.onResizeStart === 'function') {
    options.onResizeStart(e, el, { width: startWidth, height: startHeight })
  }
  
  // 鼠標(biāo)移動(dòng)處理
  const onMouseMove = (e) => {
    const deltaX = e.clientX - startX
    const deltaY = e.clientY - startY
    
    let newWidth = startWidth
    let newHeight = startHeight
    
    // 根據(jù)手柄類(lèi)型計(jì)算新尺寸
    if (handle.includes('right')) {
      newWidth = Math.max(options.minWidth, startWidth + deltaX)
      if (options.maxWidth) {
        newWidth = Math.min(options.maxWidth, newWidth)
      }
    }
    
    if (handle.includes('bottom')) {
      newHeight = Math.max(options.minHeight, startHeight + deltaY)
      if (options.maxHeight) {
        newHeight = Math.min(options.maxHeight, newHeight)
      }
    }
    
    // 應(yīng)用新尺寸
    el.style.width = `${newWidth}px`
    el.style.height = `${newHeight}px`
    
    // 觸發(fā)調(diào)整回調(diào)
    if (typeof options.onResize === 'function') {
      options.onResize(e, el, { width: newWidth, height: newHeight })
    }
  }
  
  // 鼠標(biāo)抬起處理
  const onMouseUp = (e) => {
    document.removeEventListener('mousemove', onMouseMove)
    document.removeEventListener('mouseup', onMouseUp)
    
    // 觸發(fā)結(jié)束回調(diào)
    if (typeof options.onResizeEnd === 'function') {
      options.onResizeEnd(e, el, {
        width: el.offsetWidth,
        height: el.offsetHeight
      })
    }
  }
  
  // 添加全局事件監(jiān)聽(tīng)
  document.addEventListener('mousemove', onMouseMove)
  document.addEventListener('mouseup', onMouseUp)
}

// 創(chuàng)建不同配置的指令
const vResizable = createResizableDirective()
const vResizableHorizontal = createResizableDirective({ handles: ['right'] })
const vResizableVertical = createResizableDirective({ handles: ['bottom'] })

// 用法示例
// <div v-resizable>可調(diào)整大小</div>
// <div v-resizable="{ minWidth: 200, maxWidth: 800, onResize: handleResize }">
// <div v-resizable.horizontal>僅水平調(diào)整</div>

五、最佳實(shí)踐與注意事項(xiàng)

5.1 性能優(yōu)化

// 1. 使用防抖和節(jié)流
import { debounce, throttle } from 'lodash'

const vScrollOptimized = {
  mounted(el, binding) {
    const handler = binding.value
    
    // 使用防抖
    const debouncedHandler = debounce(handler, 300, {
      leading: true,
      trailing: true
    })
    
    // 使用節(jié)流
    const throttledHandler = throttle(handler, 100)
    
    el._scrollHandler = binding.modifiers.debounce 
      ? debouncedHandler
      : binding.modifiers.throttle
        ? throttledHandler
        : handler
    
    el.addEventListener('scroll', el._scrollHandler)
  },
  
  beforeUnmount(el) {
    el.removeEventListener('scroll', el._scrollHandler)
  }
}

// 2. 事件委托
const vEventDelegation = {
  mounted(el, binding) {
    const eventType = binding.arg || 'click'
    const selector = binding.value.selector
    const handler = binding.value.handler
    
    el._delegationHandler = (e) => {
      // 檢查事件目標(biāo)是否匹配選擇器
      if (e.target.matches(selector) || e.target.closest(selector)) {
        handler(e)
      }
    }
    
    el.addEventListener(eventType, el._delegationHandler)
  },
  
  beforeUnmount(el) {
    el.removeEventListener(binding.arg || 'click', el._delegationHandler)
  }
}

// 3. 指令復(fù)用
// 創(chuàng)建可復(fù)用的基礎(chǔ)指令
const baseDirective = (customHooks = {}) => ({
  beforeMount(el, binding, vnode) {
    // 公共前置邏輯
    console.log('指令綁定到元素:', el.tagName)
    
    // 調(diào)用自定義鉤子
    if (customHooks.beforeMount) {
      customHooks.beforeMount(el, binding, vnode)
    }
  },
  
  mounted(el, binding, vnode) {
    // 公共邏輯
    el.dataset.directiveMounted = 'true'
    
    // 調(diào)用自定義鉤子
    if (customHooks.mounted) {
      customHooks.mounted(el, binding, vnode)
    }
  },
  
  // ... 其他鉤子
})

// 創(chuàng)建特定指令
const vCustomDirective = baseDirective({
  mounted(el, binding) {
    // 特定邏輯
    el.style.color = binding.value
  }
})

5.2 錯(cuò)誤處理

// 指令錯(cuò)誤處理
const vSafeDirective = {
  mounted(el, binding) {
    try {
      // 可能出錯(cuò)的操作
      const result = JSON.parse(binding.value)
      el._parsedData = result
    } catch (error) {
      // 錯(cuò)誤處理
      console.error('指令執(zhí)行錯(cuò)誤:', error)
      
      // 顯示錯(cuò)誤狀態(tài)
      el.classList.add('directive-error')
      el.title = `指令錯(cuò)誤: ${error.message}`
      
      // 觸發(fā)錯(cuò)誤事件
      el.dispatchEvent(new CustomEvent('directive-error', {
        detail: { error, binding }
      }))
    }
  },
  
  updated(el, binding) {
    // 檢查值是否有效
    if (binding.value === undefined || binding.value === null) {
      console.warn('指令接收到無(wú)效值')
      return
    }
    
    // 繼續(xù)執(zhí)行
    this.mounted(el, binding)
  },
  
  beforeUnmount(el) {
    // 清理
    el.classList.remove('directive-error')
    delete el._parsedData
  }
}

六、總結(jié)

6.1 生命周期鉤子關(guān)鍵點(diǎn)

記憶口訣

const directiveLifecycle = `

口訣一:

  • 掛載前準(zhǔn)備(beforeMount)
  • 掛載后執(zhí)行(mounted)
  • 更新前檢查(beforeUpdate)
  • 更新后響應(yīng)(updated)
  • 卸載前清理(beforeUnmount)

卸載后釋放(unmounted)

口訣二:

  • 綁定看 beforeMount + mounted
  • 更新看 beforeUpdate + updated
  • 解綁看 beforeUnmount + unmounted

口訣三:

Vue2 轉(zhuǎn) Vue3:

bind → beforeMount
inserted → mounted
update → beforeUpdate
componentUpdated → updated
unbind → beforeUnmount + unmounted

使用建議

const bestPractices = `
  • 1. 在 mounted 中操作 DOM
  • 2. 在 updated 中響應(yīng)數(shù)據(jù)變化
  • 3. 在 beforeUnmount/unmounted 中清理資源
  • 4. 使用指令參數(shù)和修飾符增強(qiáng)功能
  • 5. 考慮性能,合理使用事件監(jiān)聽(tīng)
  • 6. 保持指令的單一職責(zé)
  • 7. 添加錯(cuò)誤處理
  • 8. 提供清理函數(shù)避免內(nèi)存泄漏

6.2 完整示例:圖片懶加載指令

const vLazyLoad = {
  beforeMount(el, binding) {
    // 初始化
    el._lazyLoadObserver = null
    el._lazyLoadSrc = binding.value
    el._lazyLoadOptions = {
      root: null,
      rootMargin: '50px',
      threshold: 0.1,
      ...(typeof binding.value === 'object' ? binding.value.options : {})
    }
  },
  
  mounted(el, binding) {
    // 設(shè)置占位符
    const placeholder = el.getAttribute('data-src') || '/placeholder.jpg'
    el.setAttribute('src', placeholder)
    
    // 獲取真實(shí)圖片地址
    const src = typeof binding.value === 'string' 
      ? binding.value 
      : binding.value.src
    
    // 如果圖片已經(jīng)在視窗內(nèi),直接加載
    if (isElementInViewport(el)) {
      loadImage(el, src)
      return
    }
    
    // 使用 IntersectionObserver 懶加載
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          loadImage(el, src)
          observer.unobserve(el)
        }
      })
    }, el._lazyLoadOptions)
    
    observer.observe(el)
    el._lazyLoadObserver = observer
  },
  
  updated(el, binding) {
    // 如果圖片地址變化
    const newSrc = typeof binding.value === 'string' 
      ? binding.value 
      : binding.value.src
    
    if (newSrc !== el._lazyLoadSrc) {
      // 停止觀察
      if (el._lazyLoadObserver) {
        el._lazyLoadObserver.unobserve(el)
        el._lazyLoadObserver = null
      }
      
      // 重新設(shè)置
      el._lazyLoadSrc = newSrc
      
      // 如果已經(jīng)在視窗內(nèi),直接加載
      if (isElementInViewport(el)) {
        loadImage(el, newSrc)
      } else {
        // 重新觀察
        const observer = new IntersectionObserver((entries) => {
          entries.forEach(entry => {
            if (entry.isIntersecting) {
              loadImage(el, newSrc)
              observer.unobserve(el)
            }
          })
        }, el._lazyLoadOptions)
        
        observer.observe(el)
        el._lazyLoadObserver = observer
      }
    }
  },
  
  beforeUnmount(el) {
    // 清理 IntersectionObserver
    if (el._lazyLoadObserver) {
      el._lazyLoadObserver.unobserve(el)
      el._lazyLoadObserver = null
    }
  }
}

// 工具函數(shù)
function isElementInViewport(el) {
  const rect = el.getBoundingClientRect()
  return (
    rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.left <= (window.innerWidth || document.documentElement.clientWidth) &&
    rect.bottom >= 0 &&
    rect.right >= 0
  )
}

function loadImage(el, src) {
  const img = new Image()
  
  img.onload = () => {
    el.setAttribute('src', src)
    el.classList.add('loaded')
    
    // 觸發(fā)加載完成事件
    el.dispatchEvent(new CustomEvent('lazyloaded', {
      detail: { src, element: el }
    }))
  }
  
  img.onerror = () => {
    console.error(`圖片加載失敗: ${src}`)
    el.classList.add('error')
    
    // 觸發(fā)錯(cuò)誤事件
    el.dispatchEvent(new CustomEvent('lazyloaderror', {
      detail: { src, element: el }
    }))
  }
  
  img.src = src
}

// 用法示例
// <img v-lazy-load="'/images/picture.jpg'">
// <img v-lazy-load="{ src: '/images/picture.jpg', options: { rootMargin: '100px' } }">

核心要點(diǎn):

  • Vue 3 指令有 6 個(gè)生命周期鉤子:beforeMount、mounted、beforeUpdate、updated、beforeUnmount、unmounted
  • 正確選擇鉤子:根據(jù)操作類(lèi)型選擇合適的時(shí)間點(diǎn)
  • 資源管理:在卸載鉤子中清理事件監(jiān)聽(tīng)、定時(shí)器、觀察器等
  • 性能優(yōu)化:合理使用防抖、節(jié)流、事件委托
  • 錯(cuò)誤處理:增強(qiáng)指令的健壯性

掌握指令生命周期鉤子,你可以創(chuàng)建強(qiáng)大、可復(fù)用、高性能的自定義指令,極大地?cái)U(kuò)展 Vue 的能力邊界!

以上就是Vue自定義指令生命周期鉤子的完全指南的詳細(xì)內(nèi)容,更多關(guān)于Vue自定義指令的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

嵊州市| 鄢陵县| 安仁县| 白银市| 通渭县| 霍州市| 彩票| 沁源县| 铁岭市| 临江市| 兴化市| 望江县| 叙永县| 松滋市| 华安县| 宁化县| 扎赉特旗| 南丰县| 芜湖县| 香港| 宁安市| 永宁县| 平塘县| 周口市| 当阳市| 清苑县| 汉阴县| 三明市| 平利县| 芜湖市| 榆林市| 明光市| 永胜县| 五台县| 通山县| 宜君县| 德庆县| 临猗县| 淮南市| 镇巴县| 隆尧县|