Vue3——指令


指令是带有 v- 前缀的特殊 attribute。Vue 提供了许多内置指令。

  • 一个指令的任务是在其表达式的值变化时响应式地更新 DOM

指令的参数

  • 某些指令会需要一个参数,在指令名后通过一个冒号隔开做标识。
<a v-bind:href="url"> ... </a>
<a v-on:click="doSomething"> ... </a>
  • 指令的参数也可以是动态的,使用中括号[]包裹就可以了。
<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
<!-- 简写 -->
<a @[eventName]="doSomething"> ... </a>
  • 动态参数的限制条件
    • 动态参数中表达式的返回值应该是一个字符串,或者是 null
    • 动态参数的表达式中不应该出现空格和引号,如果必须用,建议使用计算属性来代替
<!-- 这会触发一个编译器警告 -->
<a :['foo' + bar]="value"> ... </a>
如果是直接编写在html中的模板,我们需要避免在名称中使用大写字母,因为浏览器会强制将其转换为小写:
<a :[someAttr]="value"> ... </a> 
面的例子将会在 DOM 内嵌模板中被转换为 :[someattr]。
如果变量的取值是“someAttr”是,将会在 DOM 内嵌模板中被转换为 :someattr。
且你的组件拥有 “someAttr” 属性而非 “someattr”,这段代码将不会工作。

单位文件组件没有此限制

指令的修饰符

指令也可以添加修饰符,修饰符是是以.开头的特殊后缀:

<form @submit.prevent="onSubmit">...</form>

.prevent 修饰符会告知 v-on 指令对触发的事件调用 event.preventDefault()

vue3的内置指令

渲染文本内容(v-text)

用于渲染元素的文本内容。
原理:v-text 通过设置元素的 textContent 属性来工作,因此它将覆盖元素中所有现有的内容

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

渲染html (v-html)

如果有一个变量存储一段html的字符串,可以被渲染成对应的节点,直接使用文本插值v-text的方法是没有办法将该变量渲染成html片段的
这是因为,双大括号会将数据解释为纯文本,而不是 HTML。若想插入 HTML,你需要使用 v-html 指令:

<p>Using v-html directive: <span v-html="rawHtml"></span></p>
	在单文件组件,scoped 样式将不会作用于 v-html 里的内容,
	因为 HTML 内容不会被 Vue 的模板编译器解析。
	可是使用全局的css样式来为v-html中的内容添加样式

单项绑定(v-bind)

动态的绑定一个或多个 attribute,也可以是组件的 prop
当用于组件 props 绑定时,所绑定的 props 必须在子组件中已被正确声明。

单项绑定的使用

使用
属性名之前使用 v-bind:指令,使用该指令表示该属性的属性值是一个js表达式 ,js表达式会通过vue实例获取值然后再赋值给该属性:

<div v-bind:id="dynamicId"></div>

单项绑定v-bind:的作用就是将变量赋值给标签的属性,同时标签的属性值会随着变量的变化而变化。

响应式
如果通过v-bind绑定的变量是响应式的,那么,当变量修改时就会触发v-bind重新获取最新的值,将最新的值赋值给属性。从而达到变量值一修改对应的属性值就修改的效果。这种效果正是我们想要的。

单项绑定v-bind的简写

因为 v-bind 非常常用,我们提供了特定的简写语法:, 即v-bind:属性可以直接简写成:属性
上述案例简写为

<div :id="dynamicId"></div>

同名简写

如果 attribute 的名称绑定的 JavaScript 值的名称相同,那么可以进一步简化语法,省略 attribute 值:

<!-- 与 :id="id" 相同 -->
<div :id></div>

<!-- 这也同样有效,不简写v-bind,只简写名称 -->
<div v-bind:id></div>

动态绑定多个值

如果一个标签含有多个单项绑定的属性值,可以使用v-bind的对象模式进行绑定。
eg:

const objectOfAttrs = {
  id: 'container',
  class: 'wrapper'
}

通过不带参数的v-bind可以将他们绑定到单个元素上

<div v-bind="objectOfAttrs"></div>

此时div就被绑定了名为idclass的了两个属性值

修饰符

  • .camel - 将短横线命名的 attribute 转变为驼峰式命名。

  • .prop - 强制绑定为 DOM property。

  • .attr - 强制绑定为 DOM attribute。

  • 作为prop使用的时候可以简写成.

<div :someProperty.prop="someObject"></div>
<!-- 等同于 -->
<div .someProperty="someObject"></div>

双向绑定(v-modal)

在表单输入元素或组件上创建双向绑定。也需要指定属性值,一般属性值就是value,绑定格式是v-model:value="绑定的值"

<input v-model:value="model" />

简写

因为绑定的属性值一般都是value,所以value可以简写

<input v-model="model" />

响应式

双向绑定带来的效果是:当表单元素绑定的值修改,页面上表单的输入值也会修改(v-bind也能实现);而且当页面表单的输入只修改后,表单元素绑定的值也会修改(v-bind无法实现)。

修饰符

  • .lazy - 监听 change 事件而不是 input
  • .number - 将输入的合法字符串转为数字
  • .trim - 移除输入内容两端空格

适用范围限制

v-model的使用仅限输入组件

  • <input> 会绑定 value属性,侦听input 事件;
  • <input type="checkbox"><input type="radio"> 会绑定 checked属性,侦听 change 事件;
  • <select>会绑定 value属性,侦听change 事件;
  • <textarea>会绑定 value属性,侦听input 事件;

注意事项

  • v-model 会忽略任何表单元素上初始的 value、checked 或 selected属性。它将始终将当前绑定的 JavaScript 状态视为数据的正确来源。
  • 对于需要使用 IME 的语言 (中文,日文和韩文等),你会发现 v-model 不会在 IME 输入还在拼字阶段时触发更新

使用

  • 单一复选框
    单一的复选框,绑定布尔类型值:
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>

选中时,checked 取值为true,否则为false

  • 多个复选框
<script setup>
import { ref } from 'vue'

const checkedNames = ref([])
</script>

<template>
  <div>Checked names: {{ checkedNames }}</div>

  <input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
  <label for="jack">Jack</label>
 
  <input type="checkbox" id="john" value="John" v-model="checkedNames" />
  <label for="john">John</label>
 
  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
  <label for="mike">Mike</label>
</template>

选中时就项checkedNames数组中添加元素,否则就删除元素

  • 单选按钮
<script setup>
import { ref } from 'vue'

const picked = ref('One')
</script>

<template>
  <div>Picked: {{ picked }}</div>

	<input type="radio" id="one" value="One" v-model="picked" />
	<label for="one">One</label>

	<input type="radio" id="two" value="Two" v-model="picked" />
  <label for="two">Two</label>
</template>

选中谁picked 变量就是谁的值。

  • 选择器
<div>Selected: {{ selected }}</div>

<select v-model="selected">
  <option disabled value="">Please select one</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

true-value 和 false-value

true-valuefalse-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用。这里 toggle 属性的值会在选中时被设为 'dynamicTrueValue’变量对应的值,取消选择时设为 'dynamicFalseValue’变量对应的值。

<input
  type="checkbox"
  v-model="toggle"
  :true-value="dynamicTrueValue"
  :false-value="dynamicFalseValue" />

控制元素可见性( v-show )

基于表达式值的真假性,来改变元素的可见性。
原理:v-show 通过设置内联样式的 display 属性来工作。当条件改变时,也会触发过渡效果。

条件渲染(v-if)

基于表达式值的真假性,来条件性地渲染元素。这块内容只会在指令的表达式返回真值时才被渲染。

原理 : 当 v-if 元素被触发,元素及其所包含的指令/组件都会销毁和重构。如果初始条件是假,那么其内部的内容根本都不会被渲染。

所以v-showv-if的区别是:

  • v-if会销毁元素,v-show只是修改元素的属性,如果频繁切换的话 v-if 的开销较大

  • 但是 v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事,所以单次渲染的话 v-show 的开销更大

      总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。
      因此,如果需要频繁切换,则使用 v-show 较好;
      如果在运行时绑定条件很少改变,则 v-if 会更合适。
    

<template>上的 v-if​ : 因为 v-if 是一个指令,他必须依附于某个元素。但如果我们想要切换不止一个元素呢?在这种情况下我们可以在一个 <template> 元素上使用 v-if,这只是一个不可见的包装器元素,最后渲染的结果并不会包含这个 <template> 元素。

	`v-else 和 v-else-if` 也可以在 `<template>` 上使用。

v-else

表示 v-if 链式调用的else 块,无需传入表达式。
限定:上一个兄弟元素必须有 v-ifv-else-if

<div v-if="Math.random() > 0.5">
  Now you see me
</div>
<div v-else>
  Now you don't
</div>

v-else-if

表示 v-if 的else if 块。可以进行链式调用。
限定:上一个兄弟元素必须有 v-ifv-else-if

循环渲染(v-for)

  • 基本使用
    基于原始数据多次渲染元素或模板块。
<div v-for="(item, index) in array"></div>
<div v-for="(value, key) in object"></div>
  • v-for 的 item可以使用解构赋值获取item的项
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])

// 模板中使用
<li v-for="{ message } in items">
  {{ message }}
</li>

<!-- 有 index 索引时 -->
<li v-for="({ message }, index) in items">
  {{ message }} {{ index }}
</li>
  • 循环嵌套
<li v-for="item in items">
  <span v-for="childItem in item.children">
    {{ item.message }} {{ childItem }}
  </span>
</li>
  • 也可以使用 of 作为分隔符来替代 in,这更接近 JavaScript 的迭代器语法。
  • 也可以在 <template> 标签上使用 v-for 来渲染一个包含多个元素的块。

v-for和对象

你也可以使用 v-for 来遍历一个对象的所有属性。遍历获得的结果第一个参数是属性值,第二个参数是key值,第三个参数是index值。
eg:

const myObject = reactive({
  title: 'How to do lists in Vue',
  author: 'Jane Doe',
  publishedAt: '2016-04-10'
})
<li v-for="(value, key, index) in myObject">
  {{ index }}. {{ key }}: {{ value }}
</li>

显示结果是:

在这里插入图片描述

v-for 里使用范围

v-for 可以直接接受一个整数值。在这种用例中,会将该模板基于 1…n 的取值范围重复多次。

<span v-for="n in 10">{{ n }}</span>

注意此处 n 的初值是从 1 开始而非 0。

v-if 和 v-for同时使用

当同时使用 v-ifv-for时 , v-if 比 v-for 优先级更高。我们并不推荐在一元素上同时使用这两个指令 。
v-if 比 v-for 的优先级更高,意味着处于同一节点上的 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名:

<!--
 这会抛出一个错误,因为属性 todo 此时
 没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>

解决方法:在在外新包装一层 <template> 再在其上使用 v-for 可以解决这个问题:

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

v-for的key属性

为了给 Vue 一个提示,以便它可以跟踪每个节点的标识,从而重用和重新排序现有的元素,你需要为每个元素对应的块提供一个唯一的 key。该key值最好为每个是每条数据的唯一标识,最好不要使用index作为key:具体原因点这里

<template v-for="todo in todos" :key="todo.name">
  <li>{{ todo.name }}</li>
</template

key 绑定的值期望是一个基础类型的值,例如字符串或 number 类型。

v-for和组件

我们可以直接在组件上使用 v-for,和在一般的元素上使用没有区别:

<MyComponent v-for="item in items" :key="item.id" />

但是,这不会自动将任何数据传递给组件(不会将item传递给子组件),因为组件有自己独立的作用域。如果想要将迭代后的数据传递到组件中,我们还需要自己定义props传递给子组件:

<MyComponent
  v-for="(item, index) in items"
  :item="item"
  :index="index"
  :key="item.id"
  :title="item.title"
   @remove="item.splice(index, 1)"
/>

只有title属性和remove自定义事件会传递给子组件。

事件绑定( v-on)

给元素绑定事件监听器。事件类型由参数来指定。

  • 当用于普通元素,只监听原生 DOM 事件。
  • 当用于自定义元素组件,则监听子组件触发的自定义事件。
<!-- 方法处理函数 -->
<button v-on:click="doThis"></button>

简写

v-on:可以简写成@

<!-- 方法处理函数 -->
<button @click="doThis"></button>

修饰符

  • .stop - 调用 event.stopPropagation(),阻止冒泡。
  • .prevent - 调用 event.preventDefault(),阻止默认事件。
  • .capture - 在捕获模式添加事件监听器。
  • .self - 只有事件从元素本身发出才触发处理函数。
  • .{keyAlias} - 只在某些按键下触发处理函数。
  • .once - 最多触发一次处理函数。
  • .left - 只在鼠标左键事件触发处理函数。
  • .right - 只在鼠标右键事件触发处理函数。
  • .middle - 只在鼠标中键事件触发处理函数。
  • .passive - 通过 { passive: true } 附加一个 DOM 事件。
<!-- 停止传播 -->
<button @click.stop="doThis"></button>

<!-- 阻止默认事件 -->
<button @click.prevent="doThis"></button>

<!-- 不带表达式地阻止默认事件 -->
<form @submit.prevent></form>

<!-- 链式调用修饰符 -->
<button @click.stop.prevent="doThis"></button>

<!-- 按键用于 keyAlias 修饰符-->
<input @keyup.enter="onEnter" />

动态事件

事件名对应的变量用[]括起来

<!-- 动态事件 -->
<button v-on:[event]="doThis"></button>

<!-- 使用缩写的动态事件 -->
<button @[event]="doThis"></button>

插件绑定(v-slot)

用于声明具名插槽或是期望接收 props 的作用域插槽
插槽的使用方法,可以点这里进行查看

标签限制

  • <template>标签上使用。
  • components 组件上使用
<!-- 具名插槽 -->
<BaseLayout>
  <template v-slot:header>
    Header content
  </template>

  <template v-slot:default>
    Default slot content
  </template>

  <template v-slot:footer>
    Footer content
  </template>
</BaseLayout>

<!-- 接收 prop 的具名插槽 -->
<InfiniteScroll>
  <template v-slot:item="slotProps">
    <div class="item">
      {{ slotProps.item.text }}
    </div>
  </template>
</InfiniteScroll>

<!-- 接收 prop 的默认插槽,并解构 -->
<Mouse v-slot="{ x, y }">
  Mouse position: {{ x }}, {{ y }}
</Mouse>

原样渲染( v-pre)

跳过该元素及其所有子元素的编译。
元素内具有 v-pre,所有 Vue 模板语法都会被保留并按原样渲染:如果变迁中有插值语法{{}},就会显示原始双大括号标签及内容。

<span v-pre>{{ this will not be compiled }}</span>

单次渲染(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>

缓存更新(v-memo)

用来缓存一个模板的子树。在元素和组件上都可以使用。

使用

为了实现缓存,该指令需要传入一个固定长度的依赖值数组进行比较。如果数组里的每个值都与最后一次的渲染相同,那么整个子树的更新将被跳过。
举例来说:

<div v-memo="[valueA, valueB]">
  ...
</div>

当组件重新渲染,如果 valueA 和 valueB 都保持不变,这个 <div> 及其子项的所有更新都将被跳过。

v-memo 传入空依赖数组 (v-memo=“[]”) 将与 v-once 效果相同。

与 v-for 一起使用

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>
  <p>...more child nodes</p>
</div>

默认情况下,当组件的 selected 状态改变,就会重新渲染,默认会重新创建大量的 vnode,尽管绝大部分都跟之前是一模一样的。
v-memo 用在这里本质上是在说:“只有当该项的被选中状态改变时才需要更新”。
当搭配 v-for 使用 v-memo,确保两者都绑定在同一个元素上。v-memo 不能用在 v-for 内部。

v-cloak

用于隐藏尚未完成编译的DOM模板。

问题: 当使用直接在 DOM 中书写的模板时,可能会出现一种叫做“未编译模板闪现”的情况:用户可能先看到的是还没编译完成的双大括号标签,直到挂载的组件将它们替换为实际渲染的内容。

解决: v-cloak可以解决这一问题,v-cloak在模板编译之前会保留在所绑定的元素上,直到相关组件实例被挂载后才移除。一般v-clock有一个对应的[v-clock]样式,通过该样式编写该模块在编译完成之前显示的内容。

<div v-cloak>
  {{ message }}
</div>
[v-cloak] {
  display: none;
}

直到编译完成前,<div> 将不可见。

vue3自定义指令

使用<script setup>自定义指令

  • 定义方式:一个自定义指令由一个包含类似组件生命周期钩子对象来定义。钩子函数会接收到指令所绑定元素作为其参数。
  • 命名方式:任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令,在模板中使用v-指令名进行访问。
    eg:focus
    <script setup>
    // 在模板中启用 v-focus
    const vFocus = {
      mounted: (el) => el.focus()
    }
    </script>
    <template>
      <input v-focus />
    </template>
    
    当组件渲染完成之后输入框会自动被聚焦。

指令钩子

一个指令的定义对象可以使用的钩子函数如下:

  • created: 在绑定元素的 attribute 前或事件监听器应用前调用

     created(el, binding, vnode, prevVnode) {
      },
    
  • beforeMount: 在元素被插入到 DOM 前调用

     beforeMount(el, binding, vnode, prevVnode) {
      },
    
  • mounted: 在绑定元素的父组件,及他自己的所有子节点都挂载完成后调用

     mounted(el, binding, vnode, prevVnode) {
      },
    
  • beforeUpdate: 绑定元素的父组件更新前调用

     beforeUpdate(el, binding, vnode, prevVnode) {
      },
    
  • updated:在绑定元素的父组件及他自己的所有子节点都更新后调用

     updated(el, binding, vnode, prevVnode) {
      },
    
  • beforeUnmount:绑定元素的父组件卸载前调用

     beforeUnmount(el, binding, vnode, prevVnode) {
      },
    
  • unmounted: 绑定元素的父组件卸载后调用

     unmounted(el, binding, vnode, prevVnode) {}
    

指令钩子参数

指令钩子都有4个参数:

  • el: 指令绑定到的元素,可以用于直接操作DOM
  • binding:一个对象,包含以下属性
    • value:创建递给指令的值。如v-directive="1 + 1" ,value的值就是2
    • oldValue:之前的值,仅在beforeUpdateupdate中可用。
    • arg:传递給指令的参数。如v-directive:foo 中参数是foo
    • modifiers:一个包含修饰符的对象。如在 v-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
    • instance:使用该指令的组件实例
    • dir:指令的定义对象。
  • v-node:代表绑定元素的底层VNode
  • prevNode:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。

参数案例
指令:

<div v-example:foo.bar="baz">

对应的binding参数是:

{
  arg: 'foo',
  modifiers: { bar: true },
  value: /* `baz` 的值 */,
  oldValue: /* 上一次更新时 `baz` 的值 */
}

动态自定义指令参数

<div v-example:[arg]="value"></div>

使用setup()自定义指令

在setup()中自定义指令需要使用directives选项注册,详细的注册方式可以查看Vue2定义指令的方式

export default {
  setup() {
    /*...*/
  },
  directives: {
    // 在模板中启用 v-focus
    focus: {
    }
  }
}

指令的配置方式:

 directives:{
  //指令的配置,两种写法:
     //函数写法
     指令名(element,binding){
      // element:是绑定的真实DOM元素,binding
      // binding:包含绑定的信息,包括绑定的n值
     }
     //或者键值对写法
     指令名: {
     // 指令与元素成功绑定时(一上来)调用
     bind(element,binding) {},
     // 指令所在元素被插入页面时调用
     inserted(element,binding) {},
     // 指令所在模板被重新解析是调用
     update(element,binding){

     }
   },
 }

定义全局指令

全局指令需要使用app.directive()实现:

const app = createApp({})

// 使 v-focus 在所有组件中都可用
app.directive('focus', {
  /* ... */
})

具体配置方式和setup()中配置的方式是一样的。

自定义全局指令的使用情况

只有当所需功能只能通过直接的 DOM 操作来实现时,才应该使用自定义指令。其他情况下应该尽可能地使用 v-bind 这样的内置指令来声明式地使用模板,这样更高效,也对服务端渲染更友好。

自定义指令的简化形式

对自定义指令来说一个常见的情况是需要在mountedupdated上实现相同的行为,除此之外不需要其他的钩子。

<template>
    <div v-color="color"></div>
</template>
<script>
    app.directive('color', (el, binding) => {
	  // 这会在 `mounted` 和 `updated` 时都调用
	  el.style.color = binding.value
	})
</script>

自定义指令的对象字面量值

如果你的指令需要多个值,你可以向它传递一个 JavaScript 对象字面量。指令也可以接收任何合法的 JavaScript 表达式。

<template>
   <div v-demo="{ color: 'white', text: 'hello!' }"></div>
</template>
<script>
    app.directive('demo', (el, binding) => {
	  console.log(binding.value.color) // => "white"
	  console.log(binding.value.text) // => "hello!"
	})
</script>

在组件上使用

在组件上使用自定义指令时,他始终应用于组件的根节点,和透传 attributes 类似。

<template>
   <MyComponent v-demo="test" />
</template>

MyComponent.vue

<div> <!-- v-demo 指令会被应用在此处 -->
  <span>My component content</span>
</div>

如果组件有多个根节点,指令将会被忽略且抛出一个警告。指令不能通过 v-bind="$attrs" 来传递给一个不同的元素。
不推荐在组件上使用自定义元素。

  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值