vue3 自定义指令[directives]

2,947 阅读3分钟

vue3 自定义指令[directives]

  • 自定义指令主要是为了重用涉及普通元素的底层DOM访问的逻辑。简单来说就是对复杂的dom访问逻辑的封装,更偏向js的逻辑复用,这与组件的概念是不一样的

指令基本语法

指令钩子

const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
}

指令钩子参数

  • el: 指令绑定到的元素。这可以用于直接操作DOM。
  • binding: 一个对象,包含以下属性
    • value: 传递给指令的值。例如:v-my-directive='999'中,value===999
    • arg: 传递给指令的参数 (如果有的话)。例如在 v-my-directive:loading 中,参数是 "loading"
    • instance: 使用该指令的组件实例。可以用来访问组件的变量和方法
    • oldValue: 之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用
    • modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。
  • vnode:代表绑定元素的底层 VNode。
  • prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。

指令可以做哪些事情

  • 操作渲染dom,比如vFocus、vMyHtml、数据格式转换
  • 图片懒加载
  • 上拉加载更多
  • 下拉刷新
  • ......

Directives实战

v-focus: 组件内

<input v-focus>

// export中使用directives定义组件内指令
export default {
  directives: {
    // 在模板中启用 v-focus
    focus: {
      mounted(el) {
        el.focus()
      }
    }
  }
}
// setup中任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。在上面的例子中,vFocus 即可以在模板中以 v-focus 的形式使用。
<script setup>
// 在模板中启用 v-focus
const vFocus = {
  mounted: el => el.focus()
}
</script>

v-my-html

const app = createApp({})
app.directives('myHtml',(el, binding) => {
  // 没有定义钩子则 `mounted` 和 `updated` 时都调用
  el.innerHTML = binding.value.split(/\n|\r|\r\n/).join('<br/>')
})

v-loadmore

/**
 * 指令:v-loadmore
 * @param loading: 指令回调函数执行状态 0: 可执行,1: 执行中,2: 锁定(没有更多)
 * @param threshold: 距离容器底部多少距离触发loadmore。默认:200
 * @param monitorEl: 监听dom类型 body: 监听body滚动,nowTag监听当前容器滚动。默认:body
 * @example 1: <div v-loadmore:loading="loadMore" threshold="200">...</div>     普通用法:监听body滚动
 * @example 2: <div v-loadmore:loading="loadMore" monitorEl="nowTag">...</div>  进阶用法:监听当前容器滚动。monitorEl="nowTag"为固定切必需
 */

let loading = null            // 指令函数loadmore状态:0: 可执行,1: 执行中,2: 锁定(没有更多)
let callback = null           // loadmore回调函数
let myEl = null               // 指令绑定的dom节点
let instance = null           // 使用该指令的组件实例
let threshold = 50           // 距离底部多少距离触发loadmore
let monitorEl = 'body'        // 监听dom类型 body: 监听body滚动,nowTag监听当前容器滚动
let nowTime = 0               // 当前时间戳

// 获取scrollTop
function getScrollTop () {
  if (monitorEl === 'nowTag') {
    return myEl.scrollTop
  } else {
    return document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop
  }
}

// 获取scrollHeight
function getScrollHeight () {
  if (monitorEl === 'nowTag') {
    return myEl.scrollHeight
  } else {
    return document.body.clientHeight || document.documentElement.clientHeight
  }
}

// getOffsetTop
function getOffsetTop () {
  if (monitorEl === 'nowTag') {
    return myEl.offsetTop
  } else {
    return 0
  }
}

// 获取容器高度
function getHeight () {
  if (monitorEl === 'nowTag') {
    return myEl.clientHeight
  } else {
    return window.innerHeight
  }
}

// scroll函数
function myScroll () {
  if (instance[loading] > 0) {
    return
  } else if (nowTime + 16 > + new Date()) {
    return
  } else {
    nowTime = + new Date()
  }
  let height = 0
  let offsetTop = 0
  let scrollHeight = 0
  let scroll = 0
  scroll = getScrollTop()
  scrollHeight = getScrollHeight()
  height = getHeight()
  offsetTop = getOffsetTop()
  let h = scrollHeight + offsetTop - height - scroll
  if (h < threshold && h >= 0) {
    callback()
  }
}
const app = createApp({})
app.directive('loadmore', {
  mounted (el, binding) {
    loading = binding.arg       // 获取loading变量值
    callback = binding.value
    myEl = el
    instance = binding.instance
    threshold = el.getAttribute('threshold') || 200
    monitorEl = el.getAttribute('monitorEl') || 'body'
    if (monitorEl === 'nowTag') {       // 监听元素滚动
      myEl.onscroll = myScroll
    } else {                            // 监听body滚动
      window.addEventListener('scroll', myScroll, false)
    }
  },
  unmounted () {
    if (monitorEl === 'nowTag') {
      window.removeEventListener('scroll', myScroll, false)
    }
  }
})

玫瑰花宝典