• vue2 vue3 中指令总结


    vue 内置了一些指令,也提供了自定义指令的接口。

    指令的作用:可把一些可复用的逻辑封装成指令,以实现逻辑复用,尤其是需要直接操作 DOM 时,可把这些操作封装成指令,能极大提高代码复用性和可维护性。

    指令按照使用范围看,分为全局指令局部指令(在某个组件内部使用的)。

    指令和组件一样,具有一些在特定时期执行的函数,叫作指令钩子,就是通过它们定义指令的。

    使用方式

    有一个 v-test 指令。

    <template>
      <div v-test:disabled.foo="'directive'">
        <h1>{{ msg }}h1>
      div>
    template>
    

    : 之后的是指令参数,类似 v-on:keyup 中的 keyup
    . 之后的时指令修饰符,foo 是修饰符。

    参数只能有一个,修饰符可有多个。

    v-test:disabled:boo.foo.zoo="msg"

    在指令内部,可获取到这样的对象:

    {
      arg: "disabled:boo"
      modifiers: {
        foo: true,
        zoo: true
      }
    }
    

    希望不同情况下绑定不同的参数,可使用动态参数

    v-test:[argu].foo.zoo="msg"

    参数必须在修饰符之前。

    指令的等号后面是指令表达式,其值对应 binding 对象的 value 属性。

    binding 一个对象:

    {
      arg: "disabled",
      expression: "msg",
      modifiers: {
        foo: true
      },
      name: "test",
      value: "你好",
    }
    

    vue2 指令定义方式

    以插件的形式定义一个全局 v-click-outside :

    export const clickOutside = (Vue, options) => {
      Vue.directive('clickOutside', {
        inserted(el, binding, vnode) {
          const {
            value
          } = binding
          // const _this = vnode.context
          // NOTE 技巧:处理函数挂载在元素上,方便解绑时移出事件监听
          el.onClick = ({
            target
          }) => {
            if (el.contains(target)) {
              // 点击内部
              console.log('clickInside')
            } else {
              // 点击外部
              console.log('clickOutside')
              value && value()
            }
          }
          document.addEventListener('click', el.onClick, false)
        },
        unbind(el, binding, vnode) {
          document.removeEventListener('click', el.onClick, false)
        },
      })
    }
    

    注册插件:

    main.js

    Vue.use(clickOutside)
    

    使用:

    <template>
      <div>
        <h3>测试 v-click-outsideh3>
        <div v-click-outside="onClickOutside">
          <p>测试点击外部p>
          <p>测试点击外部p>
          <p>测试点击外部p>
          <p>测试点击外部p>
        div>
    
      div>
    template>
    
    <script>
      export default {
    
        name: 'ClickOutsideDemo',
        data() {
          return {
            msg: 'Hello web components in stencil!',
          }
        },
        methods: {
          onClickOutside() {
            console.log('onClickOutside')
            console.log(this.msg)
          },
        },
    
      }
    script>
    
    

    点击到div外部时,就会执行onClickOutside

    Vue.directive('directive-name', options)
    

    options 是一个对象,包含一些生命周期钩子,这些生命周期都是可选的。

    按照执行顺序可:

    bind # 只调用一次,指令绑定到元素时调用,父元素可能不存在。做初始化工作
    ⬇️
    inserted # 只调用一次,被绑定的元素插入到父节点,父节点存在,此时可能被绑定元素还每插入文档中。
    ⬇️
    update # 此时组件还没更新完毕,拿不到最新的数据
    ⬇️
    componentUpdated # 此时组件已经更新完毕 能拿到最新
    ⬇️
    unbind # 只调用一次,指令和元素解绑,可做一些收尾工作
    

    简写方式:

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

    在 bind 和 update 时触发相同行为,而不关心其它的钩子,此时推荐使用函数方式定义指令。

    钩子的参数

    bind(el, binding, vnode)
    inserted(el, binding, vnode)
    update(el, binding, vnode, oldVnode) //  update componentUpdated 还有额外的 oldVnode
    componentUpdated(el, binding, vnode, oldVnode)
    unbind(el, binding, vnode)
    

    指令钩子函数的参数,主要关注 elbinding

    el 是绑定指令的元素,可对其进行 DOM 操作。

    binding 一个对象:

    {
      arg: "disabled",
      expression: "msg",
      modifiers: {
        foo: true
      },
      name: "test",
      value: "你好",
    }
    

    updatecomponentUpdated 钩子函数,binding 对象还有 oldArgoldValue 属性。

    指令钩子和组件生命周期的执行顺序是怎样的?

    挂载组件

    beforeCreate
    ⬇️
    created
    ⬇️
    beforeMount
    ⬇️
    bind # 指令 绑定到元素时调用,父元素可能不存在
    ⬇️
    inserted # 指令 被绑定元素插入父元素时调用,父元素一定存在
    ⬇️
    mounted
    
    

    更新组件

    beforeUpdate
    ⬇️
    update # 指令,此时组件还没更新完毕,拿不到最新的数据
    ⬇️
    componentUpdated # 指令 此时组件已经更新完毕 能拿到最新的数据
    ⬇️
    updated
    

    销毁组件

    beforeDestroy
    ⬇️
    unbind # 指令 组件在销毁之前调用,仍然能拿到组件的数据
    ⬇️
    destroyed
    

    重建组件时

    beforeCreate
    ⬇️
    created (重建)
    ⬇️
    beforeMount
    ⬇️
    bind # 注意这里,指令绑定这个钩子函数,将会拿不到重建后的最新数据
    ⬇️
    beforeDestroy (组件销毁)
    ⬇️
    unbind # 指令
    ⬇️
    destroyed
    ⬇️
    inserted # 指令 使用该钩子函数,能拿到重建后的数据
    ⬇️
    mounted
    

    结论:只有 insertedcomponentUpdated 生命周期钩子,在执行时组件的 DOM 已经更新完毕,可放心使用。它们可获取到组件更新后的数据,指令绑定的元素的父元素也已经存在。

    如何在指令生命周期中使用 this 或者访问组件实例?

    直接使用 this 为 undefined,可使用 vnode.context 获取:

    inserted(el, binding, vnode) {
        // setTile 是组件 methods 里的方法
        vnode.context.setTile(el)
      },
      componentUpdated(el, binding, vnode, oldVnode) {
        vnode.context.setTile(el)
      },
    

    vue3 中的指令

    vue3 的指令钩子、钩子的参数和定义方式有变化。

    定义方式

    全局指令

    app.directive('directive-name', directiveOptions)
    

    简写形式:

    app.directive('directive-name', (el, binding) => {}) // mounted 和 updated 操作相同
    

    局部指令

    script setup 使用 v 开头命名。

    <script setup>
      // 注册一个局部的自定义指令,需要以小写v开头
      const vFocus = {
        mounted(el, binding, vNode) {
          console.log(el)
          console.log(binding)
          console.log(vNode)
          // 获取input,并调用其focus()方法
          el.focus()
        },
      }
    script>
    
    <template>
      <input v-focus />
    template>
    

    setup 函数方式:

    <script>
      export default {
        directives: {
          focus: {
            mounted(el, binding, vNode) {
              // 获取input,并调用其focus()方法
              el.focus()
            },
          },
        },
        setup() {},
      }
    script>
    

    指令钩子的变化:

    const myDirective = {
      // 在绑定元素的 attribute 前
      // 或事件监听器应用前调用
      created(el, binding, vnode) {},
      // 在元素被插入到 DOM 前调用, 可执行一些初始化工作
      beforeMount(el, binding, vnode) {},
      // 在绑定元素的父组件
      // 及他自己的所有子节点都挂载完成后调用
      mounted(el, binding, vnode) {},
      // 绑定元素的父组件更新前调用
      beforeUpdate(el, binding, vnode, prevVnode) {},
      // 在绑定元素的父组件
      // 及他自己的所有子节点都更新后调用
      updated(el, binding, vnode, prevVnode) {},
      // 绑定元素的父组件卸载前调用,收尾工作
      beforeUnmount(el, binding, vnode) {},
      // 绑定元素的父组件卸载后调用
      unmounted(el, binding, vnode) {},
    }
    

    常用的钩子:

    beforeMount # 类似 vue2 bind
    mounted # 类似 vue2 inserted
    updated # 类似 vue2 componentUpdated
    beforeUnmount # 类似 vue2 unbind
    

    binding 的参数有变化:增加了 instance 属性用于获取组件实例。

    创建一个检测点击 div 外部的指令:

    import type { App } from 'vue'
    
    export const clickOutside = (app: App, options: any) => {
      app.directive('clickOutside', {
        mounted(el, binding) {
          const { value, instance } = binding
          // NOTE 技巧:处理函数挂载在元素上,方便解绑时移出事件监听
          el.onClick = (event: Event) => {
            if (el.contains(event.target)) {
              console.log('clickInside')
            } else {
              // 点击外部
              console.log('clickOutside')
              // console.log(instance)// 组件实例
              value && value()
            }
          }
          document.addEventListener('click', el.onClick, false)
        },
        beforeUnmount(el) {
          console.log('beforeUnmount')
          document.removeEventListener('click', el.onClick, false)
        },
      })
      return app
    }
    

    相同的功能使用 hook 实现:

    import {
      onMounted,
      onBeforeUnmount,
      ref
    } from 'vue'
    
    export function useOnClickOutside(DOM = null, callback) {
      const isClickOutside = ref(false)
    
      function handleClick(event) {
        if (DOM.value && !DOM.value.contains(event.target)) {
          callback()
          isClickOutside.value = true
        }
      }
    
      onMounted(() => {
        document.addEventListener('mousedown', handleClick)
      })
    
      onBeforeUnmount(() => {
        document.removeEventListener('mousedown', handleClick)
      })
      return isClickOutside
    }
    

    hook vs 指令

    使用方式:hook 在 script 里,指令绑定到模板

    定义方式:hook 更加简单,只需记住组件的生命钩子,复用更加方便,指令略微繁琐,需要额外记忆指令钩子。

    hook 能返回响应是对象,指令做不到。

    hook 能组合使用,指令不行。多个指令用在同一个元素上,也算一种组合?

    hook 优势更大,更加灵活

    指令钩子函数和组件生命周期的执行顺序

    挂载阶段:

    setup
    onBeforeMount
    created # 指令钩子
    beforeMount # 指令钩子
    mounted # 指令钩子
    onMounted
    

    销毁阶段:

    onBeforeUnmount
    beforeUnmount # 指令钩子
    unmounted # 指令钩子
    onUnmounted
    

    比较好实践:只定义 mounted、updated 和 beforeUnmount 钩子。

    指令和 render 函数

    指令用在 jsx 上

    和用到 html 上一样。

    function JSXDemo() {
      return (
        <>
          

    v-copy 用jsx上

    { color: 'red' }}> 点击复制

    v-auth




    按钮 ) }

    指令和 render 函数一起使用

    使用 widthDirectives 函数注册指令。

    withDirectives(VNode, [[vDirective,value, arg, modifiers]])

    function RenderDemo() {
      // withDirectives(VNode, [[vDirective,value, arg, modifiers]])
      const Input = h('input', {
        type: 'text',
        value: 'hello'
      })
      const InputWithFocus = withDirectives(Input, [
        [directiveObj.focus, true]
      ])
      const DivWithCopy = withDirectives(h('div', '这是复制的内容'), [
        [directiveObj.copy, '这是复制的内容'],
      ])
      const BtnWithAuth = withDirectives(h('button', '无权限,被禁用'), [
        [directiveObj.auth, 'li', 'disabled'],
      ])
      return h('div', [
        h('h3', 'v-copy 用h上'),
        DivWithCopy,
        ['点击复制', h('br'), InputWithFocus],
        h('br'),
        h('h3', 'v-auth'),
        BtnWithAuth,
      ])
    }
    

    不能使用全局指令

    最佳实践

    1. 传递多个值,使用对象
    <div v-demo="{ color: 'white', text: 'hello!' }">div>
    
    app.directive("demo", (el, binding) => {
      console.log(binding.value.color); // => "white"
      console.log(binding.value.text); // => "hello!"
    });
    
    1. 不在组件上使用自定义指令

    原因:当组件有多个根元素时,它不知道绑定到那个元素上。

    其他问题

    如何使用代码测试指令呢?

    我搜索不到相关教程。

    github 的一个 issue

    有请大佬解答。

    参考

    Use Vue3 Custom Directives Like a Pro

    小结

    • 指令是复用逻辑的有效方式,尤其是遇到需要直接操作 DOM 的情况。
    • vue2 中常用的指令钩子:inserted、componentUpdated 和 unbind。
    • vue3 中常用的指令钩子:mounted、updated 和 beforeUnmount。
    • vue3 中 script setup 定义局部指令使用 v 开头。
    • vue3 的 binding 对象添加了 instance 属性。
    • 相同的功能 hook 和指令都能实现,hook 优势更大。
  • 相关阅读:
    再见了,收费的云笔记,自己搭建的就是好用
    物联网python996655
    配置frp实现内网穿透并注册服务实现开机自启。
    stm32知识记录
    JavaScript 下划线转换成驼峰命名
    改进的多目标差分进化算法在电力系统环境经济调度中的应用(Python代码实现)【电气期刊论文复现】
    Swin-Transformer论文解析
    k8s异常:The node was low on resource: [DiskPressure].
    [UDS] --- ReadDataByIdentifier 0x22
    Linux系统性能监控分析工具perf
  • 原文地址:https://blog.csdn.net/JackZhouMine/article/details/139677493