• vue重走来时路之:自定义指令知多少?


    1、概述

    我们都知道在vue中主要通过组件的方式来实现代码的复用和抽象。但是有些情况下,我们还是需要对普通的DOM元素进行底层操作,那这个时候我们就需要用的vue的指令系统来完成或实现不同的功能。

    那vue指令除了内置了16个核心功能的指令以外,还允许我们创建自定义的指令(全局指令、局部指令)。

    自定义指令也能帮助我们实现部分代码功能的复用。

    2、使用方式

    • v-xxx:不带参数的指令
    • v-xxx="value":带变量值参数传入指令中
    • v-xxx="'string'":带字符串参数传入指令中
    • v-xxx:[argument]="value":增加动态argument参数到指令中
    • v-xxx:argument.modifier="value":增加修饰符到指令中

    以上为我们常用的使用方式,具体细节我们在实现自定义指令中会具体这几种方式的使用。

    3、vue内置的16个指令

    • v-text
    • v-html
    • v-pre
    • v-if
    • v-else
    • v-else-if
    • v-show
    • v-for
    • v-once
    • v-memo: 3.2版本新增指令
    • v-model
    • v-bind
    • v-on
    • v-slot
    • v-cloak
    • v-is : 已在 3.1.0 版本中被废弃。请换用带有 vue: 前缀的 is attribute。

    下面我们来详细的介绍部分指令的作用。

    v-text

    <p v-text="msg"></p>
    <!--  等价于 -->
    <p>{{ msg }}</p> 
    
    • 1
    • 2
    • 3

    v-html

    <template>
      <div v-html="vHtml"></div>
    </template>
    
    <script lang="ts" setup>
    import { ref } from 'vue';
    
    const vHtml = ref(
      '<h2>这是v-html的h2标签啊</h2><h3 style="color: red;">这是v-html的h3标签啊,红色字体</h3>'
    );
    </script> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    v-pre

    跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。

    通俗点说:比如你显示{{ xxx }}这样的文字,如果带上v-pre指令,编译的时候就不会去解析这个节点,你原本是什么样的就给你展示成什么样的

    <p v-pre>{{ this will not be compiled }}</p> 
    
    • 1

    v-show

    v-show该节点会一直存在,只是切换元素的 display

    v-once

    只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

    • 自 3.2 开始,你还可以通过 v-memo 来记住带有失效条件的部分模板。

    v-memo

    3.2新增指令

    记住一个模板的子树,元素和组件都可使用。该指令接受一个固定长度的数组作为依赖值进行记忆比对。如果数组中的每个值和上次渲染的时候相同,则整个该子树的更新都会被跳过。

    v-memo 仅供性能敏感场景的针对性优化,会用到的场景应该很少。渲染 v-for 长列表 (长度大于 1000) 可能是它最有用的场景:

    <div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
      <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
    </div>
    
    const list = reactive([
      { name: '张三', id: 1 },
      { name: '李四', id: 2 },
      { name: '王二', id: 3 },
      { name: '赵五', id: 4 },
      { name: '曹六', id: 5 },
    ]);
    const selected = ref(1); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当组件的 selected 状态发生变化时,即使绝大多数 item 都没有发生任何变化,大量的 VNode 仍将被创建。此处使用的 v-memo 本质上代表着“仅在 item 从未选中变为选中时更新它,反之亦然”。这允许每个未受影响的 item 重用之前的 VNode,并完全跳过差异比较。注意,我们不需要把 item.id 包含在记忆依赖数组里面,因为 Vue 可以自动从 item 的 :key 中把它推断出来。

    v-model

    该指令只能用于:inputtextareaselectcomponents这几个标签

    修饰符有以下几种:

    • .lazy:把默认的监听 input 事件改为监听 change
    • .number:输入字符串转为有效的数字
    • .trim:去除输入的首尾空格
    <input type="text" v-model.trim="value" placeholder="请输入" /> 
    
    • 1

    v-on

    绑定事件监听器。事件类型由参数指定。表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略。

    缩写为:@[event]=“xxx”

    修饰符有以下几种:

    • .stop:阻止单击事件继续冒泡
    • .prevent:阻止浏览器默认行为
    • .capture:添加事件侦听器时使用事件捕获模式
    • .self:只执行直接作用在该元素身上的事件,会忽略其他元素的冒泡或者捕获事件
    • .once:事件只会触发一次
    • .passive:告诉浏览器不用去查询,我们没用preventDefault阻止默认行为
    • .{keyAlias}:仅当事件是从特定键触发时才触发回调。
    • .left:只当点击鼠标左键时触发。
    • .right:只当点击鼠标右键时触发。
    • .middle:只当点击鼠标中键时触发。

    上面几个修饰符详细介绍可以看我另外一篇文章:修饰符详细介绍

    <!-- 方法处理器 -->
    <button v-on:click="doThis"></button>
    
    <!-- 动态事件 -->
    <button v-on:[event]="doThis"></button>
    
    <!-- 内联语句 -->
    <button v-on:click="doThat('hello', $event)"></button>
    
    <!-- 缩写 -->
    <button @click="doThis"></button>
    
    <!-- 动态事件缩写 -->
    <button @[event]="doThis"></button>
    
    <!-- 停止冒泡 -->
    <button @click.stop="doThis"></button>
    
    <!-- 阻止默认行为 -->
    <button @click.prevent="doThis"></button>
    
    <!-- 阻止默认行为,没有表达式 -->
    <form @submit.prevent></form>
    
    <!-- 串联修饰符 -->
    <button @click.stop.prevent="doThis"></button>
    
    <!-- 键修饰符,键别名 -->
    <input @keyup.enter="onEnter" />
    
    <!-- 点击回调只会触发一次 -->
    <button v-on:click.once="doThis"></button>
    
    <!-- 对象语法 -->
    <button v-on="{ mousedown: doThis, mouseup: doThat }"></button> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    v-bind

    动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。

    缩写为: :. (当使用 .prop 修饰符时)

    修饰符有以下几种:

    • .camel:将 kebab-case attribute 名转换为 camelCase。
    • .attr:将一个绑定强制设置为一个 DOM attribute。(3.2新增
    • .prop:将一个绑定强制设置为一个 DOM property。(3.2新增
    <!-- 绑定 attribute -->
    <img v-bind:src="imageSrc" />
    
    <!-- 动态 attribute 名 -->
    <button v-bind:[key]="value"></button>
    
    <!-- 缩写 -->
    <img :src="imageSrc" />
    
    <!-- 动态 attribute 名缩写 -->
    <button :[key]="value"></button>
    
    <!-- 内联字符串拼接 -->
    <img :src="'/path/to/images/' + fileName" />
    
    <!-- class 绑定 -->
    <div :class="{ red: isRed }"></div>
    <div :class="[classA, classB]"></div>
    <div :class="[classA, { classB: isB, classC: isC }]"></div>
    
    <!-- style 绑定 -->
    <div :style="{ fontSize: size + 'px' }"></div>
    <div :style="[styleObjectA, styleObjectB]"></div>
    
    <!-- 绑定一个全是 attribute 的对象 -->
    <div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
    
    <!-- prop 绑定。"prop" 必须在 my-component 声明 -->
    <my-component :prop="someThing"></my-component>
    
    <!-- 将父组件的 props 一起传给子组件 -->
    <child-component v-bind="$props"></child-component>
    
    <!-- XLink -->
    <svg><a :xlink:special="foo"></a></svg> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    v-slot

    提供具名插槽或需要接收 prop 的插槽。

    插槽名 (可选,默认值是 default)

    缩写为:#

    适用于:template标签上面

    <template #default></template> 
    
    • 1

    v-cloak

    这个指令保持在元素上直到关联组件实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到组件实例准备完毕。

    作用:为了防止在页面加载时先出现变量名闪烁的情况,造成不好的用户体验

    例如:{{ msg }} (闪一下)=> hello

    用法:

    • html中: {{ msg }}
    • css中[v-cloak] { display:none }

    一定要搭配css一起用,一定要搭配css一起用,一定要搭配css一起用

    <div v-cloak>{{ msg }}</div>
    <style lang="scss">
    [v-cloak] {
      display: none;
    }
    </style> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    以上这些就是部分内置指令的介绍和用法。

    4、自定义指令有哪些钩子函数?

    • created:在绑定元素的 attribute 或事件监听器被应用之前调用。在指令需要附加在普通的 v-on 事件监听器调用前的事件监听器中时,这很有用。
    • beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用。
    • mounted:在绑定元素的父组件被挂载后调用。
    • beforeUpdate:在更新包含组件的 VNode 之前调用。
    • updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用。
    • beforeUnmount:在卸载绑定元素的父组件之前调用。
    • unmount:当指令与元素解除绑定且父组件已卸载时,只调用一次。

    5、那这些指令钩子函数传递了哪些参数呢?

    • el:指令绑定到的元素。这可用于直接操作 DOM。

    • binding:包含以下 property 的对象。

      • instace:使用指令的组件实例。
      • value:传递给指令的值。例如,在 v-my-directive="1 + 1" 中,该值为 2
      • oldValue:先前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否有更改都可用。
      • arg:传递给指令的参数(如果有的话)。例如在 v-my-directive:foo 中,arg 为 "foo"
      • modifiers:包含修饰符(如果有的话) 的对象。例如在 v-my-directive.foo.bar 中,修饰符对象为 {foo: true,bar: true}
      • dir:一个对象,在注册指令时作为参数传递。
    • vnode:一个真实 DOM 元素的蓝图,对应上面收到的 el 参数。

    • prevNode:上一个虚拟节点,仅在 beforeUpdate 和 updated 钩子中可用。

    6、注册自定义指令分为全局注册和局部注册

    全局注册

    app.directive('my-directive', {
      mounted: (el) => {
        el.style.background = '#4598d2';
      },
    }); 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    setup中局部注册

    setup中局部注册自定义指令必须以 vNameOfDirective 的形式来命名本地自定义指令,以使得它们可以直接在模板中使用。

    // Directive是从vue导出的一个ts类型声明
    const vMyDirective: Directive = {
      mounted: (el) => {
        el.style.background = 'red';
      },
    }; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    7、我们来实现一个注册在全局的防抖切换背景色的自定义指令

    1. 首页我们在项目根目录创建文件夹: directives
    2. 然后在 directives 目录创建 index.tsbackground.tsdebounce.ts 三个文件
    3. index.tsbackground.tsdebounce.ts 三个文件代码如下:
    // background.ts
    import { Directive } from 'vue';
    
    export const background: Directive = (el, binding) => {
      el.style.background = binding.value;
    };
    
    export default background; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    // debounce.ts
    import { Directive } from 'vue';
    
    export const debounce: Directive = {
      mounted(el, binding) {
        let timer: number;
        el.addEventListener('click', () => {
          timer && clearTimeout(timer);
          timer = window.setTimeout(() => binding.value(), 300);
        });
      },
    };
    
    export default debounce; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    // index.ts
    import { App, Directive } from 'vue';
    import debounce from '@/directives/debounce';
    import background from '@/directives/background';
    
    const directives = {
      debounce,
      background,
    };
    
    export default {
      install(app: App) {
        Object.keys(directives).forEach((key) => {
          // 这里是批量注册指令
          app.directive(key, (directives as { [key: string]: Directive })[key]);
        });
      },
    }; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. 然后我们在项目的 main.ts 代码中引入我们刚才创建的 index.ts,并通过 app.use()注册进去,代码如下:
    import { createApp } from 'vue';
    import App from './App.vue';
    
    import directives from './directives';
    const app = createApp(App);
    
    app.use(directives).mount('#app'); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 至此,我们两个自定义指令就可以在组件中愉快的使用了。
    2. 使用方式如下:
    // template
    <el-button v-debounce="click">这里是防抖指令,快速点击试试:{{ debounceText }}</el-button>
    <el-button v-background="background" style="color: white" @click="changeBackground">
      这里是设置背景色指令:{{ background }}
    </el-button> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    // setup
    const debounceText = ref(new Date().getTime());
    const click = () => (debounceText.value = new Date().getTime());
    
    const background = ref('#4598d2');
    const changeBackground = () =>
      (background.value = `#${('00000' + ((Math.random() * 0x1000000) << 0).toString(16)).substr(-6)}`); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 自定义指令的实现到此就结束了哦

    8、结尾

    推荐大家有空可以自己动手试试,毕竟百看不如一试加深体验。

  • 相关阅读:
    滴滴可观测平台 Metrics 指标实时计算如何实现了又准又省?
    通讯录多版本代码归纳
    05、Python 简单计算器和进制转换
    自然语言处理历史史诗:NLP的范式演变与Python全实现
    Unity学习笔记---材质纹理&天空盒
    全网最全Java快捷键~
    计算机图像处理-中值滤波
    uniapp+腾讯地图定位获取位置信息
    CNN的实现与可视化
    不会还有人不知道for循环还可以这么用吧(范围for)
  • 原文地址:https://blog.csdn.net/web2022050902/article/details/125520724