指令注册

自定义指令可以通过全局注册或局部注册两种方式创建。选择哪种方式取决于指令的使用范围。

全局注册

全局注册的指令可以在任何组件中使用:

Vue.directive('focus', {
  inserted: function(el) {
    el.focus()
  }
})

使用:

<template>
  <div>
    <input v-focus>
  </div>
</template>

注册时机

全局注册通常在入口文件中进行:

import Vue from 'vue'
import App from './App.vue'

Vue.directive('focus', {
  inserted(el) {
    el.focus()
  }
})

new Vue({
  render: h => h(App)
}).$mount('#app')

批量注册

将多个指令放在一个文件中统一注册:

import Vue from 'vue'

const directives = {
  focus: {
    inserted(el) {
      el.focus()
    }
  },
  color: {
    bind(el, binding) {
      el.style.color = binding.value
    }
  },
  permission: {
    inserted(el, binding) {
      if (!binding.value) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    }
  }
}

Object.keys(directives).forEach(key => {
  Vue.directive(key, directives[key])
})

局部注册

局部注册的指令只能在当前组件中使用:

<template>
  <div>
    <input v-focus>
  </div>
</template>

<script>
export default {
  directives: {
    focus: {
      inserted(el) {
        el.focus()
      }
    }
  }
}
</script>

组件内使用

<template>
  <div>
    <p v-highlight="'yellow'">高亮文本</p>
  </div>
</template>

<script>
export default {
  directives: {
    highlight: {
      bind(el, binding) {
        el.style.backgroundColor = binding.value
      }
    }
  }
}
</script>

函数简写

当只需要 bind 和 update 时执行相同逻辑,可以使用函数简写:

Vue.directive('color', function(el, binding) {
  el.style.color = binding.value
})

等价于:

Vue.directive('color', {
  bind(el, binding) {
    el.style.color = binding.value
  },
  update(el, binding) {
    el.style.color = binding.value
  }
})

对象字面量

如果指令需要多个值,可以传入对象:

<div v-demo="{ color: 'white', text: 'hello' }"></div>

<script>
Vue.directive('demo', {
  bind(el, binding) {
    console.log(binding.value.color)  // 'white'
    console.log(binding.value.text)   // 'hello'
  }
})
</script>

注册插件方式

将指令封装成插件:

const MyDirectives = {
  install(Vue) {
    Vue.directive('focus', {
      inserted(el) {
        el.focus()
      }
    })
    
    Vue.directive('click-outside', {
      bind(el, binding, vnode) {
        el.clickOutsideEvent = function(event) {
          if (!(el === event.target || el.contains(event.target))) {
            binding.value(event)
          }
        }
        document.body.addEventListener('click', el.clickOutsideEvent)
      },
      unbind(el) {
        document.body.removeEventListener('click', el.clickOutsideEvent)
      }
    })
  }
}

export default MyDirectives

使用插件:

import Vue from 'vue'
import MyDirectives from './directives'

Vue.use(MyDirectives)

全局 vs 局部选择

使用全局注册:

  • 指令在多个组件中使用
  • 通用性强的指令(如 focus、permission)
  • 项目级的基础指令

使用局部注册:

  • 指令只在特定组件中使用
  • 组件特有的功能
  • 避免全局污染

命名规范

Vue.directive('my-directive', {})  // 短横线命名

// 使用时
<div v-my-directive></div>

注意:注册时使用短横线命名,使用时加上 v- 前缀。

完整示例

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <h2>全局指令</h2>
    <input v-focus placeholder="自动聚焦">
    
    <h2>局部指令</h2>
    <p v-highlight="'#ffeb3b'">高亮文本</p>
  </div>

  <script>
    Vue.directive('focus', {
      inserted(el) {
        el.focus()
      }
    })

    new Vue({
      el: '#app',
      directives: {
        highlight: {
          bind(el, binding) {
            el.style.backgroundColor = binding.value
            el.style.padding = '10px'
          }
        }
      }
    })
  </script>
</body>
</html>

注意事项

  1. 命名冲突:避免与内置指令重名
  2. 全局污染:谨慎使用全局注册
  3. 性能考虑:指令中避免频繁 DOM 操作
  4. 清理工作:在 unbind 钩子中清理事件监听器

掌握指令注册后,接下来学习钩子函数,了解指令的生命周期。