vue2.x之指令

一、什么是指令

Vue指令是Vue.js框架中的一种特殊语法,用于在HTML模板中直接操作DOM。通过使用Vue指令,我们可以对DOM元素进行动态的绑定、条件渲染、列表渲染等操作,实现数据驱动的交互效果。

⏰ 指令:

1. 本质就是自定义属性
2. Vue中指定都是以 v- 开头,不管是内置指令,还是自定义指令

二、内置指令

条件渲染指令

条件渲染相关指令主要包括v-ifv-else-ifv-elsev-show这几个,用于条件性地渲染、隐藏一块内容。

v-if 系列

⏰ 基于表达式值的真假性,来条件性地渲染元素或者模板片段。

我们来直接看代码会更好理解:

<div v-if="type === 'A'">Type A</div>
<div v-else-if="type === 'B'">Type B</div>
<div v-else>Default Type</div>

模板最终生成,我们可以这样理解:

function genThisHTML(scopeData) {
  // scopeData 为 Vue 实例里绑定的 data 数据
  if (scopeData.type === "A") {
    return `<div>Type A</div>`;
  } else if (scopeData.type === "B") {
    return `<div>Type B</div>`;
  } else {
    return `<div>Default Type</div>`;
  }
}

这样一看,是不是很清晰明朗。条件渲染指令其实是将常见的 Javascript 语法,通过 HTML 属性的形式附加在模板里,然后在 Vue 编译器编译的时候识别出来,然后匹配对应的执行逻辑而已。

key:使用v-if指令有个需要注意的地方

Vue 中的虚拟 DOM 算法,在 Diff 过程中会优先使用现有的元素进行调整,而并非删除原有的元素再重新插入一个元素。这样的算法背景下,当我们绑定的数据发生变更时,可能会存在这样的情况:

<template v-if="type === 'phone'">
  <input type="number" placeholder="Enter your phone" />
</template>
<template v-else>
  <input type="text" placeholder="Enter something" />
</template>

当我们的typephone切换到其他值的时候,该<input>元素只会更新属性值typeplaceholder,但原先输入的内容还在:

  • 未绑定key前,更新type前的 DOM 元素

  • 未绑定key前,更新type后的 DOM 元素

如果我们希望能精确命中对应的元素,可以通过绑定key的方式:

<template v-if="type === 'phone'">
  <input type="number" placeholder="Enter your phone" key="phone" />
</template>
<template v-else> <input type="text" placeholder="Enter something" key="something-else" /> </template>

这种情况下,input 会根据key是否匹配,来控制是否重新渲染(即移除元素再重新插入)。可以理解为我们给有这样特殊需要的 input 添加了个性化的 ID,它不跟其他 input 共享页面中的 HTML 元素:

  • 绑定key后,更新type前的 DOM 元素

  • 绑定key后,更新type后的 DOM 元素

上面我们只讲了v-ifv-else-ifv-else这几个,条件渲染的指令还有一个v-show

<div v-show="isShow">Something</div>

v-show

v-showv-if不一样,v-if会在条件具备的时候才进行渲染,而v-show的逻辑是一定渲染,但在条件具备的时候才显示:

function genVShowHTML(scopeData) {
  // scopeData 为 Vue 实例里绑定的 data 数据
  // 这里的 hide 类名具有样式 display: none;
  return `<div ${scopeData.isShow ? "" : 'class="hide"'}>Something</div>`;
}

带有v-show的元素始终会被渲染并保留在 DOM 中。

v-show v-if 区别:
一般来说,v-if有更高的切换开销(因为要不停地重新渲染),而v-show有更高的初始渲染开销。
因此,如果需要非常频繁地切换,则使用v-show较好;如果在运行时条件很少改变,则使用v-if较好。

列表渲染指令

列表渲染相关的指令主要是v-for这个指令,用来渲染列表。

v-for

v-for指令需要使用item in items形式的特殊语法,除了遍历数组以外,v-for还能遍历对象、数字:

  • 遍历数组时

    <!-- 其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名,可选的第二个参数 index 为当前项的索引 -->
    <ul>
      <li v-for="(item, index) in items">
        {{ index }}: {{ item.message }}
      </li>
    </ul>

    模板最终生成,我们可以这样理解:

    // 遍历数组的可以解析成这样
    function genVForArrayHTML(scopeData) {
      // scopeData 为 Vue 实例里绑定的 data 数据
      let htmlString = "<ul>";
      scopeData.items.forEach((item, index) => {
        htmlString += `<li>${index}: ${item.message}</li>`;
      });
      htmlString += "</ul>";
      return htmlString;
    }
  • 遍历对象时
    <!-- 在遍历对象时,会按 Object.keys() 的结果遍历 -->
    <!-- 其中 object 是源数据对象,而 value 则是被遍历的对象值,可选的第二个参数 key 为当前值的键名,可选的第三个参数 index 为当前项的索引 -->
    <div v-for="(value, key, index) in object">
      {{ index }}.{{ key }}: {{ value }}
    </div>
    模板最终生成,我们可以这样理解
    // 遍历对象的可以解析成这样
    function genVForObjectHTML(scopeData) {
      // scopeData 为 Vue 实例里绑定的 data 数据
      let htmlString = "";
      Object.keys(scopeData.object).forEach((key, index) => {
        htmlString += `<div>${index}.${key}: ${scopeData.object[key]}</div>`;
      });
      return htmlString;
    }
  • 还能遍历数字
    <p v-for="n in 10">{{n}}</p>
    模板最终生成,我们可以这样理解
    // 遍历数字的可以解析成这样
    function genVForNumberHTML(num) {
      let htmlString = "";
      for (let i = 1; i <= num; i++) {
        htmlString += `<p>${i}</p>`;
      }
      return htmlString;
    }

使用v-for指令有个需要注意的地方

⏰ key

同样的,由于 Vue 中虚拟 DOM 的 Diff 方法和更新页面的方式,v-for指令渲染也会存在上面v-if一样的问题,即 input 这样的依赖临时 DOM 状态或子组件状态的元素,需要使用key来绑定使得可以重新渲染:

<div v-for="item in items" v-bind:key="item.id">
  <!-- 内容 -->
</div>

数据更新检测

在 Vue 中,当我们在data里绑定对象或者数组的时候,需要注意以下问题:

(1) data中的对象:Vue 无法检测到对象属性的添加或删除,当实例被创建时就已经存在于data中的属性才是响应式的,新增的属性等都不会触发视图的更新。

(2) data中的数组:除了特殊的数组操作如push()pop()shift()unshift()splice()sort()reverse()这些方法之外,数组中某个元素被替换、更新这种操作是无法触发视图更新的。

对于上面这两种情况,我们一般可以使用以下处理方式:

// 数组处理方法1: 返回新数组
this.items = [...this.items, newItem];
// 数组处理方法2: Vue.set 或 vm.$set
Vue.set(vm.items, indexOfItem, newValue);
vm.$set(vm.items, indexOfItem, newValue);

// 对象处理方法1: 返回新对象 this.object = { ...this.object, key: newValue }; // 对象处理方法2: Vue.set 或 vm.$set Vue.set(vm.object, key, value); vm.$set(vm.object, key, value);

⚠️注意:另外,当v-ifv-for处于同一节点,v-for的优先级比v-if更高,这意味着v-if将分别重复运行于每个v-for循环中。(不推荐同时使用v-ifv-for,因为会对可读性产生影响。)

双向绑定指令

v-model

v-model指令在表单<input><textarea><select>元素上创建双向数据绑定。实际上v-model是语法糖,它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理:

<template>
  <input v-model="val" />
  <!-- v-model 指令其实是下面的语法糖 -->
  <input :value="val" @input="updateValue" />
  <!-- 也可以这么写 -->
  <input :value="val" @input="val = $event.target.value" />
</template>
<script>
  export default {
    data() {
      return {
        val: ""
      };
    },
    methods: {
      updateValue(event) {
        this.val = event.target.value;
      }
    }
  };
</script>

使用 Tips:

v-model使用在多选或者选择框上时,需要注意的是:

(1) 多选时,v-model会绑定到一个数组。
(2) 对于单选按钮,复选框及选择框的选项,v-model绑定的值通常是静态字符串。
(3) 复选框可以使用true-valuefalse-value来设置绑定的值。

<!-- 当选中时,`picked` 为字符串 "a" -->
<input type="radio" v-model="picked" value="a" />

<!-- `toggle` 为 truefalse -->
<input type="checkbox" v-model="toggle" />
<!-- `toggle` 为 'yes''no' -->
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />

<!-- 当选中第一个选项时,`selected` 为字符串 "abc" -->
<select v-model="selected">
  <option value="abc">ABC</option>
</select>

修饰符

除此之外,v-model还支持修饰符:

自定义 v-model

我们在很多场景下,需要对一些表单组件封装一些逻辑,如日期选择、常见的搜索功能等。前面也说过,v-model是语法糖:

<input v-model="something" />
<!-- 其实相当于下面的简写 -->
<input :value="something" @input="something = $event.target.value" />

所以我们如果需要自定义v-model,需要做两个事情:

(1) 接受一个value prop。
(2) 在有新的值时触发input事件并将新值作为参数。

默认情况下,一个组件的v-model会使用valueProp 和input事件。但是诸如单选框、复选框之类的输入类型可能把value用作了别的目的。model选项可以避免这样的冲突:

Vue.component("my-checkbox", {
  model: {
    prop: "checked", // 绑定的值
    event: "change"  // 自定义事件
  },
  props: {
    checked: Boolean,
    // 这样就允许拿 `value` 这个 prop 做其它事了
    value: String
  }
  // ...
});

很多时候,我们会直接使用开源的库,例如 DatetimePicker。很多以前的工具库都依赖了 jQuery(说明它是真的好用),而开发业务的我们通常没有特别多的时间去一个个造轮子,我们会直接拿别人造好的轮子来用。这里我们来讲述下一个使用 select2 插件的自定义下拉组件的封装:

<template>
  <div>
    <select
      class="form-control"
      :placeholder="placeholder"
      :disabled="disabled"
    ></select>
  </div>
</template>

<script>
  export default {
    name: "Select2",
    data() {
      return {
        select2: null
      };
    },
    model: {
      event: "change", // 使用change作为自定义事件
      prop: "value" // 使用value字段,故这里其实不用写也可以
    },
    props: {
      placeholder: {
        type: String,
        default: ""
      },
      options: {
        type: Array,
        default: []
      },
      disabled: {
        type: Boolean,
        default: false
      },
      value: null
    },
    watch: {
      options(val) {
        // 若选项改变,则更新组件选项
        this.setOption(val);
      },
      value(val) {
        // 若绑定值改变,则更新绑定值
        this.setValue(val);
      }
    },
    methods: {
      setOption(val = []) {
        // 更新选项
        this.select2.select2({ data: val });
        // 若默认值为空,且选项非空,则设置为第一个选项的值
        if (!this.value && val.length) {
          const { id, text } = val[0];
          this.$emit("change", id);
          this.$emit("select", { id, text });
          this.select2.select2("val", [id]);
        }
        // 触发组件更新状态
        this.select2.trigger("change");
      },
      setValue(val) {
        this.select2.select2("val", [val]);
        this.select2.trigger("change");
      }
    },
    mounted() {
      // 初始化组件
      this.select2 = $(this.$el)
        .find("select")
        .select2({
          data: this.options
        })
        .on("select2:select", ev => {
          const { id, text } = ev["params"]["data"];
          this.$emit("change", id);
          this.$emit("select", { id, text });
        });
      // 初始化值
      if (this.value) {
        this.setValue(this.value);
      }
    },
    beforeDestroy() {
      // 销毁组件
      this.select2.select2("destroy");
    }
  };
</script>

需要注意的是change自定义事件和value绑定值相关的内容,而关于 Select2 和 jQuery 相关的,大家可以去搜索一下对应的使用方式。而这个select2组件也被封装打包成一个 npm 依赖包了,对源码感兴趣的可以搜索v-select2-component的 npm 包来查看。

事件绑定指令

v-on

v-on指令是事件绑定指令,当用户需要点击,按下键盘,滚动鼠标等操作时,想要添加一些自己的逻辑处理时,就可以为特定的元素绑定一些特定的事件。

我们先在被vue实例挂载的标签div中,放入一个按钮为input标签,在标签内部写入v-on指令,冒号(:)后面就是事件名,等号(=)后面就是需要绑定的事件名称。

<div id="app">
<input type="button" value="事件绑定" v-on:事件名="方法名">
</div>

v-on的简写形式

在绑定的事件都是v-on开头,Vue其实提供了一个简洁的写法,用@符号替代v-on

<div id="app">
<input type="button" value="事件绑定" @click="todo">
<input type="button" value="事件绑定" @mouseenter="todo">
<input type="button" value="事件绑定" @dblclick="todo">
</div>

 v-on事件函数中传入参数 

1、如果事件直接绑定函数名称,那么默认会传递事件对象作为事件函数的第一个参数 

<!-- 1、如果事件直接绑定函数名称,那么默认会传递事件对象作为事件函数的第一个参数 -->
<button v-on:click='handle1'>点击1</button>
<!--Methods配置项中的方法-->>
handle1: function(event) {
    console.log(event.target.innerHTML)
},

2、如果事件绑定函数调用,那么事件对象必须作为最后一个参数显示传递,并且事件对象的名称必须是$event 

<!-- 2、如果事件绑定函数调用,那么事件对象必须作为最后一个参数显示传递,并且事件对象的名称必须是$event -->
<button v-on:click='handle2(123, 456, $event)'>点击2</button>
handle2: function(p, p1, event) {
  console.log(p, p1)
  console.log(event.target.innerHTML)
  this.num++;
}

v-on事件修饰符

在Vue中事件修饰符主要有:
.stop:等同于JavaScript中的event.stopPropagation(),防止事件冒泡
.prevent:等同于JavaScript中的event.preventDefault(),防止执行预设的行为(如果事件可取消, 则取消该事件,而不停止事件的进一步传播)
.capture:与事件冒泡的方向相反,事件捕获由外到内
.self:只会触发自己范围内的事件,不包含子元素
.once:只会触发一次
  • .stop 防止事件冒泡

    冒泡事件:嵌套两三层父子关系,然后每层都有点击事件,当点击最内层的子节点,就会触发从内至外子节点->父节点的点击事件。
    <!-- HTML --> 
    <div id="app"> 
            <div class="outeer" @click="outer"> 
                <div class="middle" @click="middle"> 
                    <button @click="inner">点击我(^_^)</button>
                </div>
            </div> 
            <p>{{ message }}</p> 
    </div>
     let app = new Vue({
        el: '#app',
        data () { 
            return { message: '测试冒泡事件' };
        }, 
        methods: { 
            inner: function () {
                this.message = 'inner: 这是最里面的Button' 
            }, 
            middle: function () { 
                this.message = 'middle: 这是中间的Div' 
            }, 
            outer: function () { 
                this.message = 'outer: 这是外面的Div' 
            } 
        } 
    })

    防止冒泡事件的写法是:点击事件加上.stop相当于在每个方法中调用了等同于event.stopPropagation(),点击子节点不会捕获到父节点的事件。

    <!-- HTML --> 
    <div id="app"> 
        <div class="outeer" @click.stop="outer"> 
            <div class="middle" @click.stop="middle"> 
                <button @click.stop="inner">点击我(^_^)</button>
            </div>
        </div> 
    </div>
  • .prevent取消默认事件

    .prevent等同于JavaScript的event.preventDefault(),用于取消默认事件。

    默认事件指对DOM的操作会引起自动执行的动作,比如点击超链接的时候会进行页面的跳转,点击表单提交按钮时会重新加载页面等,使用".prevent"修饰符可以阻止这些事件的发生。

    <a href="http://www.baidu.com" @click.prevent="doSomething"></a>

    此时点击超链接不会进行页面的跳转。

  • .capture 捕获事件

    捕获冒泡,即有冒泡发生时,有该修饰符的dom元素会先执行,如果有多个,从外到内依次执行,然后再按自然顺序执行触发的事件。
    <!--template模版-->>
    <div style="width: 500px; height: 500px; background-color: aqua;" @click.capture="outer">
      <div style="width: 200px; height: 200px; background-color: #a71d5d;" @click="middle">
          <div style="width: 100px; height: 100px; background-color: #404040;" @click="inner"></div>
      </div>
    </div>
    <!--methods配置项-->>
    methods: {
      inner: function () {
        console.log('this is the most inner one')
      },
      middle: function () {
        console.log('this is inner')
      },
      outer: function () {
        console.log('this is outer')
      }
    },

    此时点击最内层div,结果如下:

    多个捕获事件 :

    <!--template模版-->>
    <div style="width: 500px; height: 500px; background-color: aqua;" @click.capture="outer">
      <div style="width: 200px; height: 200px; background-color: #a71d5d;" @click.capture="middle">
          <div style="width: 100px; height: 100px; background-color: #404040;" @click="inner"></div>
      </div>
    </div>
    <!--methods配置项-->>
    methods: {
      inner: function () {
        console.log('this is the most inner one ')
      },
      middle: function () {
        console.log('this is inner')
      },
      outer: function () {
        console.log('this is outer')
      }
    },

    点击最内层结果:

  • . self:

    将事件绑定到自身,只有自身才能触发,通常用于避免冒泡事件的影响。
    <!--template模版-->>
    <div style="width: 500px; height: 500px; background-color: aqua;" @click="outer">
      <div style="width: 200px; height: 200px; background-color: #a71d5d;" @click.self="middle">
          <div style="width: 100px; height: 100px; background-color: #404040;" @click="inner"></div>
      </div>
    </div>
    <!--methods配置项-->>
    methods: {
      inner: function () {
        console.log('this is the most inner one')
      },
      middle: function () {
        console.log('this is inner')
      },
      outer: function () {
        console.log('this is outer')
      }
    },

    此时点击最内层:

     
  • .once 只执行一次点击
    如果我们在@click事件上添加.once修饰符,只要点击按钮只会执行一次。

⏰v-on按键修饰符

在做项目中有时会用到键盘事件,在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符

记住所有的keyCode比较困难,所以Vue为最常用的键盘事件提供了别名:

.enter:回车键
.tab:制表键
.delete:含delete和backspace键
.esc:返回键
.space: 空格键
.up:向上键
.down:向下键
.left:向左键
.right:向右键
<!-- 只有在 `keyCode` 是 13 时调用 `vm.submit()` --> <input v-on:keyup.13="submit">
<!-- -当点击enter 时调用 `vm.submit()` --> <input v-on:keyup.enter="submit">
<!--当点击enter或者space时 时调用 `vm.alertMe()` --> <input type="text" v-on:keyup.enter.space="alertMe" >

<script>
    var vm = new Vue({
        el:"#app",
        methods: {
} })
</script>

⏰ v-on 鼠标修饰符修饰符

鼠标修饰符用来限制处理程序监听特定的滑鼠按键。常见的有:

.left:鼠标左键
.middle:鼠标中间滚轮
.right:鼠标右键

⏰ v-on 自定义按键修饰符别名

在Vue中可以通过config.keyCodes自定义按键修饰符别名。例如,由于预先定义了keycode 116(即F5)的别名为f5,因此在文字输入框中按下F5,会触发prompt方法,出现alert。

<!-- HTML -->
<div id="app">
    <input type="text" v-on:keydown.f5="prompt()">
</div>

Vue.config.keyCodes.f5 = 116;

let app = new Vue({
    el: '#app',
    methods: {
        prompt: function() {
            alert('我是 F5!');
        }
    }
});

属性绑定指令

v-bind

v-bind 是 Vue.js 中的一个重要指令,它用于动态地绑定一个或多个属性,或一个组件 prop 到表达式。

在默认情况下,如果没有使用 v-bind,属性值被当作纯字符串来处理。这意味着如果你尝试绑定一个对象或变量,它们会被直接转换为字符串。使用 v-bind,你可以绑定各种类型的值,包括对象,数组,数字,字符串,布尔值,甚至 JavaScript 表达式。

🌰例如,你可以使用 v-bind 来绑定 href 属性:

<a v-bind:href="url">链接</a>

在这个例子中,url 是一个变量,v-bind:href 会把 a 标签的 href 属性和 url 变量绑定在一起。当 url 变量的值变化时,href 属性也会更新。

还可以使用 v-bind 来绑定样式:

<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

在这个例子中,activeColor 和 fontSize 是变量,v-bind:style 会把 div 标签的颜色和字体大小与这两个变量绑定在一起。当这两个变量的值变化时,颜色和字体大小也会更新。

⏰ v-bind简写

: 或者 . (当使用 .prop 修饰符)
值可以省略 (当 attribute 和绑定的值同名时) 3.4+

总的来说,v-bind 指令可以让你创建动态的、响应式的绑定,使得你的 UI 能够与数据保持同步。

⏰ 修饰符

.camel - 将短横线命名的 attribute 转变为驼峰式命名。
.prop - 强制绑定为 DOM property。3.2+
.attr - 强制绑定为 DOM attribute。3.2+

⏰ 示例

<!-- 绑定 attribute -->
<img v-bind:src="imageSrc" />

<!-- 动态 attribute 名 -->
<button v-bind:[key]="value"></button>

<!-- 缩写 -->
<img :src="imageSrc" />

<!-- 缩写形式的动态 attribute 名 (3.4+),扩展为 :src="src" -->
<img :src />

<!-- 动态 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” 必须在子组件中已声明。 -->
<MyComponent :prop="someThing" />

<!-- 传递子父组件共有的 prop -->
<MyComponent v-bind="$props" />

<!-- XLink -->
<svg><a :xlink:special="foo"></a></svg>

内容渲染指令

内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下 2 个:v-text,v-html

v-text

更新元素的文本内容。

v-text 指令用于操作纯文本,它会替代显示对应的数据对象上的值。当绑定的数据对象上的值发生改变,插值处的内容也会随之更新。
v-text 指令的简写形式为 {{}},只支持单向绑定,同时可以支持逻辑运算。
v-text 指令与 Vue.js 插值表达式语法略有区别,v-text 指令会覆盖元素中原本的内容,但是插值表达式只会替换自己的这个占位符。

🌰示例:

<span v-text="msg"></span>
<!-- 等同于 -->
<span>{{msg}}</span>

v-html

更新元素的 innerHTML。

v-html 的内容直接作为普通 HTML 插入—— Vue 模板语法是不会被解析的。如果你发现自己正打算用 v-html 来编写模板,不如重新想想怎么使用组件来代替。

🌰示例:
 在某些时候我们不希望直接输出<a href='http://www.baidu.com'>百度一下</a>这样的字符串,而输出被html自己转化的超链接。此时可以使用v-html。

<!--template 模版-->>
<template>
  <div>
    <h2>插值语法:{{ url }}</h2>
    <h2 v-html=" 'v-html指令:'+url"></h2>
  </div>
</template>

<!--script data配置项-->
data() {
  return {
    url:"<a href='http://www.baidu.com'>百度一下</a>"
  };
},

其它内置指令

v-slot

在Vue中, v-slot 指令用于定义插槽的模板内容。它用于在父组件中传递内容到子组件中的插槽。 v-slot 指令可以用于 标签或组件标签上,以便在子组件中使用插槽。

⏰ 使用 v-slot 指令时,可以使用以下两种语法:

  • 缩写语法: # 符号表示 v-slot 指令,后面跟插槽名称。
    <template #插槽名称>
      <!-- 插槽内容 -->
    </template>
  • 完整语法: v-slot 指令后面跟着 : ,后面是插槽名称。
    <template v-slot:插槽名称>
      <!-- 插槽内容 -->
    </template>

⏰ v-slot 指令的使用场景包括但不限于以下几种:

  • 场景一:在组件中使用插槽,将父组件中的内容传递给子组件。

    父组件
    <template>
      <div>
        <child-component>
          <template v-slot:default>
          <!-- 插槽内容 -->
            <p>This is the content passed from the parent component.</p>
          </template>
        </child-component>
      </div>
    </template>

    子组件

    <template>
      <div>
        <slot></slot>
      </div>
    </template>
  • 场景二:在子组件中使用具名插槽,根据插槽名称渲染不同的内容。
    父组件

    <template>
      <div>
        <child-component>
          <template v-slot:header>
            <!-- 插槽内容 -->
            <h1>Header Content</h1>
          </template>
          <template v-slot:body>
          <!-- 插槽内容 -->
            <p>Body Content</p>
            </template>
        </child-component>
      </div>
    </template>

    子组件

    <template>
      <div>
        <slot name="header"></slot>
        <slot name="body"></slot>
      </div>
    </template>
  • 场景三:在子组件中使用作用域插槽,将子组件中的数据传递到父组件中进行渲染。

    父组件

    <template>
      <div>
        <child-component>
          <template v-slot:default="slotProps">
            <!-- 插槽内容 -->
            <p>{{ slotProps.message }}</p>
          </template>
        </child-component>
      </div>
    </template>

    子组件

    <template>
      <div>
        <slot :message="message"></slot>
      </div>
    </template>
    
    <script>
      export default {
        data() {
          return {
            message: "Hello from child component!"
          };
        }
      };
    </script>

v-pre

v-pre 指令用于跳过这个元素和它的子元素的编译过程。

⏰ 用于包含大量静态内容的元素,提高渲染性能。

🌰示例:

<div v-pre>
  <p>{{ willNotBeInterpreted }}</p>
</div>

v-once

v-once 指令用于渲染元素和组件一次。渲染后,不再随数据变化重新渲染。(可以想象成‘单例对象’,只进行首次的渲染,之后就不变了)

⏰ 在随后的重新渲染,元素/组件及其所有子项将被当作静态内容并跳过渲染。这可以用来优化更新时的性能。

<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>

<!-- 带有子元素的元素 -->
<div v-once>
  <h1>Comment</h1>
  <p>{{msg}}</p>
</div>

<!-- 组件 -->
<MyComponent v-once :comment="msg" />

<!-- `v-for` 指令 -->
<ul>
  <li v-for="i in list" v-once>{{i}}</li>
</ul>

❗️ 从 3.2 起,你也可以搭配 v-memo 的无效条件来缓存部分模板。

v-cloak

v-cloak 指令常用在插值表达式的标签中,用于解决当网络加载很慢或者频繁渲染页面时,页面显示出源代码的情况。

⏰ 所以为了提高用户的体验性,使用指令 v-cloak,搭配着 CSS 一起使用,在加载时隐藏挂载内容,等到加载完毕再显示渲染后的数据。

先通过CSS 属性选择器的方式,那就是拿到 v-cloak 这个属性,设置隐藏挂载内容。等到数据渲染完成后,v-cloak 属性会自动去除,页面会显示最终效果。v-cloak的使用

  • 在插值语法所在的标签处加上v-cloak指令
    <h1 v-cloak> {{ name }} </h1>
  • 在 css 中设置 v-cloak 的属性为 display: none
    <style type="text/css">
      [v-cloak] {
       display: none;
      }
    </style>

三、自定义指令

除了内置指令以外,Vue 也支持自定义指令。通常我们会在需要给某个元素添加简单的事件处理逻辑的时候,会使用到自定义指令。

使用场景

我们来看看简单的一个表单自动聚焦的例子:

// 注册一个全局自定义指令 `v-focus`
// 当然这里也支持通过 Vue 选项来进行局部注册指令
Vue.directive("focus", {
  // 当被绑定的元素插入到 DOM 中时
  inserted: function(el) {
    // 聚焦元素
    el.focus();
  }
});

然后我们可以在模板中任何元素上使用新的v-focus属性:

<!-- 当页面加载时,该元素将获得焦点 -->
<input v-focus />

指令的生命周期函数

export default {
    // 这个函数只会被调用一次,就是绑定元素的时候,可以在这里做初始化工作
    bind(el,binding,vnode,oldVode){
        // el 绑定的 dom 元素
        // bingding 一些参数,很有用的,建议去读一下官方文档,传递的一些参数也在里面
        // vnode 虚拟 dom
        // oldVode 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用
    },
    // 被绑定的元素 插入父元素的时候调用该函数--仅保证父节点存在
    inserted(el,binding,vnode,oldVode){
        
    },
    // 所在组件的 vnode 更新时调用
    update(el,binding,vnode,oldVode){
        
    },
    // 指令所在的组件的 vnode 及其 子组件的 vnode 全部更新之后调用
    componentUpdated(el,binding,vnode,oldVode){
        
    },
    // 指令与元素解绑时调用,只调用一次
    unBind(el,binding,vnode,oldVode){
        
    }
}

❗️这里是把指令书写在单独的js文件中

定义自定义指令

自定义指令-局部

  • 第一种:直接在 directives 中书写自定义指令
    <template>
      <div>
        用户名: <input type="text" v-default-value="val">
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          val: 'username'
        }
      },
        
    // 自定义指令
      directives: {
        defaultValue: function (el, binding) {
          console.log(binding);
          el.value = binding.value   //  binding.value 的值  保存的是指令的 等号 后面的属性值
        }
      }
    };
        
    </script>
  • 第二种:指令书写在单独的js文件中

    指令书写在 src 新建一个 directives 文件夹,在里面新建一个 focus.js 文件。
    export default {
        // 被绑定元素插入父节点的调用
        inserted(el) {
            el.focus()
        },
    }

    组件:在组件中引入书写指令的 js 模块,并且在 directives 选项中注册即可使用。

    <template>
      <div>
        用户名: <input v-focus type="text">
      </div>
    </template>
    
    <script>
    import focus from "@/utils/directives/focus";
    export default {
      directives: {
        focus,
      }
    };
    </script>
  • 第三种:动态指令参数

    写法:v-mydirective:[argument]="value"

    就是可以给指令传递一些参数,argument 是指令的参数,value 是指令的属性值,argument 可以在 binding 参数的 arg 中拿到,value 可以在 binding.value 中拿到,value 可以是基础数据类型,也可以是复杂数据类型,数组,对象等。
    export default {
        bind(el, binding) {
            console.log(binding);
            el.style[binding.arg] = binding.value + "px"
        }
    }

    组件:在组件中引入书写指令的 js 模块,并且在 directives 选项中注册即可使用。

    <template>
      <div v-fixed:[direction]="300" class="box"></div>
    </template>
    
    <script>
    import fixed from "@/utils/directives/fixed";
    export default {
      data() {
        return {
          direction: 'right',
        }
      },
    
      directives: {
        fixed,
      }
    };
    </script>
    
    <style lang="scss" scoped>
    .box {
      height: 200px;
      width: 200px;
      background-color: skyblue;
      position: fixed;
    }
    </style>

⏰ 自定义指令-全局

  • 直接在 man.js 中定义指令

    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    
    // 这里定义的是全局指令
    Vue.directive("font-color", {
      bind(el, binding) {
        el.style.color = binding.value
      }
    })
    
    new Vue({
      router,
      store,
      render: h => h(App)
    }).$mount('#app')

    使用:全局指令无需注册,直接在组件中使用即可。

    <template>
      <div v-font-color="colorVal">
        文字文字
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          colorVal: '#00ff00'
        };
      }
    };
    </script>

函数式自定义指令和对象式自定义指令

自定义指令可以按照两种方式去配置:函数式和对象式

⏰ 函数式自定义指令

函数式指令就是将指令定义成回调函数,这样在触发的时候就会触发回调函数,回调函数包含两个参数:el和binding。

  • 基本写法

    <template>
      <div class="test-wrapper">
        <div v-test>自定义指令</div>
      </div>
    </template>
    <script>
    export default {
      data () {
        return {
        };
      },
      directives: {
        test(el, binding) {
          console.log('el', el);
          console.log('binding', binding);
        }
      }
    };

    根据打印信息,我们可以看到el表示dom元素,binding表示包含元素有关的所有属性的对象

  • 传递参数

    指令也是可以传递参数的

    <template>
      <div class="test-wrapper">
        <div v-test="number">自定义指令</div>
        <div>number的值: {{number}}</div>
        <button @click="addNumber">增加number</button>
      </div>
    </template>
    <script>
    export default {
      data () {
        return {
          number: 1
        };
      },
      directives: {
        test(el, binding) {
          console.log('el', el);
          console.log('binding', binding);
        }
      },
      methods: {
        addNumber() {
          this.number++;
        }
      }
    };
    </script>

    参数binding的value属性可以拿到传递过来的参数。效果如下:

    如果这个参数被组件中别的部分修改了,那么指令也会被执行,而且能拿到最新的值。比如增加number的值,参数也变了。效果如下:

⏰ 对象式自定义指令

对象式也就是在指定定义对象中添加几个钩子函数,这些钩子都是可选的。

  • bind: 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
  • inserted: 被绑定元素插入父节点时调用(仅保证父节点存在,但不一定被插入文中)
  • update:所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前。指令的值可能发生变化也有可能没有。
  • componentUpdated:指令所在组件的VNode及其子VNode全部更新后调用
  • unbind: 只调用一次,指令与元素解绑时调用

这些钩子函数具有以下参数:

  • el: 指令所绑定的元素,可以用来直接操作DOM
  • binding: 一个包含元素属性的对象
  • vnode: Vue编译生成的虚拟节点
  • oldVnode: 上一个虚拟节点,仅在update和componentUpdated钩子中使用
<template>
  <div class="test-wrapper">
    <div>number的值: {{number}}</div>
    <button @click="addNumber">增加number</button>
    <input type="text" v-test="number">
  </div>
</template>
<script>
export default {
  data () {
    return {
      number: 1
    };
  },
  directives: {
    test: {
      bind(el, binding) {
        el.value = binding.value;
      },
      inserted(el, binding) {
        el.focus();
      },
      update(el, binding) {
        el.value = binding.value;
      }
    }
  },
  methods: {
    addNumber() {
      this.number++;
    }
  }
};
</script>

初始化的时候,会执行bind和inserted,效果如下:

 改变number的值,会执行update,效果如下:

小结

函数式定义指令相当于是简写版,只会在初始化和模板变化(解析)的时候被触发,而对象式可以根据钩子函数在特定的时候执行对应的操作

指令定义的时候不加v-, 但使用时要加v-

指令名如果是多个单词,要使用kebab-case命名方式,不要用驼峰命名。

posted on 2024-07-09 17:37  梁飞宇  阅读(33)  评论(0)    收藏  举报