Skip to content

自定义指令

文档

Vue2 自定义指令

Vue3 自定义指令

Vue2 自定义指令

js
/*
Vue2.x 自定义指令生命周期钩子
  bind             : 只调用一次,指令第一次绑定到元素时调用
  inserted         : 被绑定元素插入父节点时调用
  update           : 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。
  componentUpdated : 指令所在组件的 VNode 及其子 VNode 全部更新后调用
  unbind           : 只调用一次,指令与元素解绑时调用

自定义指令生命周期钩子的参数
  el               : 被绑定指令的元素 DOM 对象
  bindings         : 是一个对象
    name           : 不包括 v- 前缀的指令名称 my-show
    oldValue       : 更新前的指令绑定的值
    value          : 当前指令绑定的值
    expression     : 指令表达式的值 v-my-show="1 + 1" ->  表达式为: "1 + 1"
    arg            : 指令的参数, 一个字符串 v-my-show:abc -> abc
    modifiers      : 指令的修饰符集合 v-my-show.trim.number -> { trim: true, number: true }
  vNode            : 绑定指令的元素的 虚拟节点
  prevNode         : 上一个虚拟节点
*/

// 全局指令
Vue.directive('my-show', myShow);
Vue.directive('my-if', myIf);

// 局部/实例指令
export default {
  directives: {
    myShow,
    myIf,
  },
};

Vue3 自定义指令

js
/*
Vue3.x 自定义指令生命周期钩子
  beforeMount   : 指令绑定的元素挂载前调用
  mounted       : 指令绑定的元素挂载后调用
  beforeUpdate  : 指令绑定的组件数据更新前调用
  updated       : 指令绑定的组件数据更新后调用
  beforeUnmount : 指令绑定的组件卸载前调用
  unmounted     : 指令绑定的组件卸载后调用

自定义指令生命周期钩子的参数
  el            : 被绑定指令的元素 DOM 对象
  bindings      : 是一个对象
    arg         : 指令的参数, 一个字符串 v-my-show:abc -> abc
    dir         : 指令对象的所有属性
    instance    : 使用这个指令的实例
    modifiers   : 指令的修饰符集合 v-my-show.trim.number -> { trim:true, number: true }
    oldValue    : 更新前的指令绑定的值
    value       : 当前指令绑定的值
  vNode         : 绑定指令的元素的 虚拟节点
  prevNode      : 上一个虚拟节点, beforeUpdate & upated 可用
*/

// 全局指令
app.directive('my-show', myShow);
app.directive('my-if', myIf);

// 局部/实例指令
export default {
  directives: {
    myShow,
    myIf,
  },
};

Vue2 手动实现 v-if 和 v-show

html
<template>
  <div>
    <div>
      <button @click="toggleShow">toggleShow</button>
      <button @click="toggleIf">toggleIf</button>
    </div>
    <!-- 
      <div class="box box1" v-show="showBox1">show</div>
      <div class="box box2" v-if="showBox2">if</div>
    -->
    <div class="box box1" v-my-show="showBox1">show</div>
    <div class="box box2" v-my-if="showBox2">if</div>
  </div>
</template>

<script>
  const myShow = function (el, bindings) {
    el.style.display = bindings.value ? '' : 'none';
  };

  const myIf = {
    inserted(el, bindings) {
      // 插入到父元素的时候判断, 是渲染注释节点还是 el
      // 由于需要在更新的时候获取到 commentNode 所以
      // 需要将 commentNode 挂到 el 上, 因为更新的时候
      // 也会传入这个 el, 因为是同一个引用, 所以在更新时候
      // 就能获取到 commentNode
      el.commentNode = document.createComment('v-if');
      if (bindings.value) {
        el.parentNode.replaceChild(el, el.commentNode);
      } else {
        el.parentNode.replaceChild(el.commentNode, el);
      }
    },
    componentUpdated(el, bindings) {
      // 只有指令绑定的值变化了才执行
      if (bindings.value === bindings.oldValue) {
        return;
      }
      if (bindings.value) {
        el.commentNode.parentNode.replaceChild(el, el.commentNode);
      } else {
        el.parentNode.replaceChild(el.commentNode, el);
      }
    },
  };

  export default {
    directives: {
      myShow,
      myIf,
    },
    data: () => ({
      showBox1: false,
      showBox2: false,
    }),
    methods: {
      toggleShow() {
        this.showBox1 = !this.showBox1;
      },
      toggleIf() {
        this.showBox2 = !this.showBox2;
      },
    },
  };
</script>

<style scoped>
  .box {
    width: 100px;
    height: 100px;
  }
  .box1 {
    background: #f00;
  }
  .box2 {
    background: #0f0;
  }
</style>

Vue3 手动实现 v-if 和 v-show

html
<template>
  <div>
    <div>
      <button @click="toggleShow">toggleShow</button>
      <button @click="toggleIf">toggleIf</button>
    </div>
    <!-- 
      <div class="box box1" v-show="showBox1">show</div>
      <div class="box box2" v-if="showBox2">if</div>
    -->
    <div v-my-show="showBox1" class="box box1">v-my-if</div>
    <div v-my-if="showBox2" class="box box2">v-my-show</div>
  </div>
</template>

<script>
  const myShow = function (el, bindings) {
    // 因为 mounted 和 updated 是一样的操作, 所以可以直接赋值一个方法
    // 这个方法在 mounted 和 updated 时, 都会执行
    el.style.display = bindings.value ? '' : 'none';
  };

  const myIf = {
    mounted(el, bindings) {
      // 要想在update中获取到 commentNode, 所以需要将 commentNode
      // 挂到 el 上, 然后在 updated 钩子中获取到 el.commentNode
      // 因为是同一个引用, 所以可以获取到
      el.commentNode = document.createComment('v-if');
      if (bindings.value) {
        el.parentNode.replaceChild(el, el.commentNode);
      } else {
        el.parentNode.replaceChild(el.commentNode, el);
      }
    },
    updated(el, bindings) {
      if (bindings.value !== bindings.oldValue) {
        // 当 v-my-if 绑定的值更新了才更新
        if (bindings.value) {
          el.commentNode.parentNode.replaceChild(el, el.commentNode);
        } else {
          el.parentNode.replaceChild(el.commentNode, el);
        }
      }
    },
  };

  export default {
    directives: {
      myIf,
      myShow,
    },
    data: () => ({
      showBox1: false,
      showBox2: false,
    }),
    methods: {
      toggleShow() {
        this.showBox1 = !this.showBox1;
      },
      toggleIf() {
        this.showBox2 = !this.showBox2;
      },
    },
  };
</script>

<style lang="css" scoped>
  .box {
    width: 100px;
    height: 100px;
  }
  .box1 {
    background: #f00;
  }
  .box2 {
    background: #0f0;
  }
</style>

Released under the MIT License.