美文网首页
31.自定义指令

31.自定义指令

作者: 静昕妈妈芦培培 | 来源:发表于2021-09-02 08:45 被阅读0次

    认识自定义指令

    在Vue的模板语法中我们学习过各种各样的指令:v-show、v-for、v-model等等,除了使用这些指令之外,Vue
    也允许我们来自定义自己的指令

    • 注意:在Vue中,代码的复用和抽象主要还是通过组件;
    • 通常在某些情况下,你需要对DOM元素进行底层操作,这个时候就会用到自定义指令
    自定义指令分为两种:
    • 自定义局部指令:组件中通过 directives 选项,只能在当前组件中使用;
    • 自定义全局指令:app的 directive 方法,可以在任意组件中被使用;

    比如我们来做一个非常简单的案例:当某个元素挂载完成后可以自定获取焦点

    实现方式一:如果我们使用默认的实现方式;
    <template>
      <div>
        <input type="text" ref="inputRef" />
      </div>
    </template>
    
    <script>
    import {ref, onMounted} from "vue"
    export default {
      setup() {
        const inputRef = ref(null)
        onMounted(() => {
          inputRef.value.focus()
        })
        return {inputRef};
      },
    };
    </script>
    
    <style scoped></style>
    
    
    实现方式二:自定义一个 v-focus 的局部指令;
    <template>
      <div>
        <input type="text" v-focus />
      </div>
    </template>
    
    <script>
    export default {
      directives: {
        focus: {
          mounted(el, bindings, vnode, preVNode) {
            el.focus();
          },
        },
      },
      setup() {
        return {};
      },
    };
    </script>
    
    <style scoped></style>
    
    
    实现方式三:自定义一个 v-focus 的全局指令;

    main.js

    import { createApp } from "vue";
    import App from "./4_directives/1.input自动获取焦点.vue";
    const app = createApp(App);
    app.directive("focus", {
       mounted(el, bindings, vnode, preVNode) {
         el.focus();
       },
     });
    app.mount("#app");
    

    App.vue

    <template>
      <div>
        <input type="text" v-focus />
      </div>
    </template>
    
    <script>
    export default {
      setup() {
        return {};
      },
    };
    </script>
    
    <style scoped></style>
    
    

    指令的生命周期

    一个指令定义的对象,Vue提供了如下的几个钩子函数:

    • created:在绑定元素的 attribute 或事件监听器被应用之前调用;
    • beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用;
    • mounted:在绑定元素的父组件被挂载后调用;
    • beforeUpdate:在更新包含组件的 VNode 之前调用;
    • updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用;
    • beforeUnmount:在卸载绑定元素的父组件之前调用;
    • unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次;
    <template>
      <div>
        <input type="text" v-focus />
      </div>
    </template>
    
    <script>
    export default {
      directives: {
        focus: {
          created(el, bindings, vnode, preVNode) {
            //此时元素的属性和事件还没绑定
            console.log("created", "此时元素的属性和事件还没绑定");
          },
          beforeMount(el, bindings, vnode, preVNode) {
            console.log("beforeMount", "此时绑定元素的父组件还没挂载");
          },
          mounted(el, bindings, vnode, preVNode) {
            //此时绑定元素的父组件已挂载
            console.log("mounted", "此时绑定元素的父组件已挂载");
            el.focus();
          },
          beforeUpdate(el, bindings, vnode, preVNode) {
            console.log("beforeUpdate", "在更新包含的组件的vnode之前调用");
          },
          undated(el, bindings, vnode, preVNode) {
            console.log("updated", "在包含组件的 VNode 及其子组件的 VNode 更新后调用;");
          },
          beforeUnmount(el, bindings, vnode, preVNode) {
            console.log("beforeUnmount", "在卸载绑定元素的父组件之前调用");
          },
          unmounted(el, bindings, vnode, preVNode) {
            console.log("unmounted", "当指令与元素解除绑定且父组件已卸载时,只调用一次;");
          }
        },
      },
      setup() {
        return {};
      },
    };
    </script>
    
    <style scoped></style>
    
    

    指令的参数和修饰符

    如果我们指令需要接受一些参数或者修饰符应该如何操作呢?

    <template>
      <div>
        <button v-why:info.aaa.bbb="{ name: 'coderwhy', age: '18' }">
          {{ count }}
        </button>
      </div>
    </template>
    
    <script>
    import { ref } from "@vue/reactivity";
    export default {
      directives: {
        why: {
          mounted(el, bindings, vnode, preVNode) {
            //此时绑定元素的父组件已挂载
            console.log("mounted", "此时绑定元素的父组件已挂载");
            console.log(bindings);
          },
        },
      },
      setup() {
        const count = ref(100);
        return { count };
      },
    };
    </script>
    
    <style scoped></style>
    
    
    
    • info是参数的名称;
    • aaa-bbb是修饰符的名称;
    • 后面是传入的具体的值
    在我们的生命周期中,我们可以通过 bindings 获取到对应的内容:
    image.png

    例:自定义时间格式化指令

    ./directives/time-format.js

    import dayjs from 'dayjs'
    
    export default function (app) {
      app.directive('format-time', {
        //在绑定的元素的父组件被挂载后调用
        mounted(el,bindings,vnode,preVnode) {
          //获取绑定元素的内容
          const textContent = el.textContent
          let timestamp = parseInt(textContent)
    
          //假如时间戳为10位说明单位为s,为13为,说明单位为毫秒
          if(textContent.length === 10) {
            timestamp = timestamp * 1000
          }
          //获取v-format-time的值
          let formatString = bindings.value
          //假如v-format-time的值不存在,设置默认值
          if(!formatString) {
            formatString = 'YYYY-MM-DD  HH:mm:ss'
          }
    
    
          el.textContent =  dayjs(timestamp).format(formatString)
        }
      })
    
    }
    

    ./directives/index.js

    import timeFormat from './time-format'
    
    export default function registerDirectives(app) {
      timeFormat(app)
    }
    

    在main.js中注册

    import { createApp } from "vue";
    import App from "./4_directives/3.指令的参数和修饰符.vue";
    import registerDirectives from "./directives/index";
    const app = createApp(App);
    
    app.use(registerDirectives);
    app.mount("#app");
    

    使用

    <template>
      <div>
        <!-- 此处需要用单引号引起来,否则会认为YYYY-MM-DD是个变量 -->
        <div v-format-time="'YYYY-MM-DD'">1624777616</div>
      </div>
    </template>
    
    <script>
      export default {
        
      }
    </script>
    
    

    例:input值格式化
    ./directives/nuber-input.js

    function onInput(el, elInput, bindings, vnode) {
      return function handle() {
        // 只保留数字
        //获取input输入框的值
        let val = elInput.value;
        // modifiers为修饰符对象,传入了float,则其float属性为true
        //假如使用了.float修饰符,输入框可以输入带小数点的浮点数
        if (bindings.modifiers.float) {
          // 清除"数字"和"."以外的字符
          val = val.replace(/[^\d.]/g, "");
          // 只保留第一个, 清除多余的  ..   ...  ....等都清除  .保留
          val = val.replace(/\.{2,}/g, ".");
          // 第一个字符如果是.号,则补充前缀0
          val = val.replace(/^\./g, "0.");
    
          //假如指令有传值,如:v-number-input.float="2"
          if (typeof bindings.value !== "undefined") {
            // 期望保留的最大小数位数
            let pointKeep = 0;
            if (
              typeof bindings.value === "string" ||
              typeof bindings.value === "number"
            ) {
              pointKeep = parseInt(bindings.value);
            }
            if (!isNaN(pointKeep)) {
              //如果pointkeep是有效数字
              if (!Number.isInteger(pointKeep) || pointKeep < 0) {
                //如果pointkeep不是整数或者是负数 设置pointkeep为0
                pointKeep = 0;
              }
              const str = "^(\\d+)\\.(\\d{" + pointKeep + "}).*$";
              const reg = new RegExp(str);
              if (pointKeep === 0) {
                // 不需要小数点
                val = val.replace(reg, "$1");
              } else {
                // 通过正则保留小数点后指定的位数
                val = val.replace(reg, "$1.$2");
              }
            }
          }
          if (Number(val) > 10000000) val = 10000000;
          elInput.value = val + "";
        } else {
          //假如没有使用.float修饰符,输入框只能输入整数  v-number-input 输入框所有非数字替换为空字符串
          elInput.value = elInput.value.replace(/[\D]/g, "");
        }
      };
    }
    
    export default function(app) {
      app.directive("number-input", {
        mounted(el, bindings, vnode, preVnode) {
          console.log(vnode);
          //获取input元素:假如指令绑定的元素不是input元素,获取其包含的input元素
          const elInput = el.tagName === "INPUT" ? el : el.querySelector("input");
    
          //给input元素添加事件
          elInput.addEventListener(
            "keyup",
            onInput(el, elInput, bindings, vnode),
            false
          );
        },
      });
    }
    
    
    <input v-number-input />
    <input v-number-input.float='2' />
    

    此文档主要内容来源于王红元老师的vue3+ts视频教程

    相关文章

      网友评论

          本文标题:31.自定义指令

          本文链接:https://www.haomeiwen.com/subject/bnpliltx.html