Skip to content

Latest commit

 

History

History
785 lines (645 loc) · 18 KB

内置指令和自定义指令.md

File metadata and controls

785 lines (645 loc) · 18 KB

📚 目录

  1. vue内置指令
    1. v-text
    2. v-html
    3. v-pre
    4. v-cloak
    5. v-once
    6. v-for
    7. v-if 和 v-else 和 v-if-else
    8. v-show
    9. v-model
      1. 表单上使用v-model
      2. 组件上使用v-model
      3. 使用.sync修饰符实现v-model等效
    10. v-bind
    11. v-on
    12. v-slot
      1. 匿名插槽
      2. 具名插槽
      3. 作用域插槽
  2. 自定义指令
    1. 全局自定义指令
    2. 局部自定义指令
    3. 动态参数和简写

Vue.js指令 (Directives) 是带有 v- 前缀的特殊特性。指令特性的值预期是单个 JavaScript 表达式 (v-for 是例外情况)。我们可以将指令看作特殊的HTML特性,作用是:当表达式的值改变时,将其产生的连带影响,响应式地作用于绑定的DOM。

1. vue内置指令

内置指令指的就是Vue.js自带的指令,开箱即用,一共有14个:

  • 内容渲染指令
    • v-text
    • v-html
    • v-pre
    • v-cloak
    • v-once
  • 条件渲染指令
    • v-for
    • v-if
    • v-else
    • v-else-if
    • v-show
  • 属性绑定指令
    • v-model
    • v-bind
    • v-on
  • 插槽指令
    • v-slot

1-1. v-text

该指令的作用是更新DOM元素的文本内容,使用率很低,一般都是用双花括号来代替了,可以忽略

语法如下:

<template>
  <div>
    <span v-text="msg"></span>
  </div>
</template>

<script>
export default {
  name: "home",
  data() {
    return {
      msg: '我是一个文本信息'
    }
  }
}
</script>

1-2. v-html

该指令的作用是更新DOM元素的innerHTML,内容按普通HTML插入,用处挺多的,比如渲染字符串html,动态插入元素等。(滥用可能会引起XSS攻击,比如用户提交"<a href="https://有害网站">xxx</a>",会被渲染成html标签)

语法如下:

<template>
  <div>
    <div v-html="span"></div>
  </div>
</template>

<script>
export default {
  name: "home",
  data() {
    return {
      span: '<div><span>我是span</span></div>'
    }
  }
}
</script>

1-3. v-pre

该指令不需要表达式,作用是跳过绑定了这个指令的元素和它的子元素的编译过程。跳过大量没有指令的节点会加快编译,但是绑定了后,会直接显示双花括号{{ xxx }},按需使用。

1-4. v-cloak

该指令不需要表达式。这个指令会保持在dom元素上,直到其关联的实例结束编译才失效。作用是配合css选择器[v-cloak],来实现隐藏未编译完成的元素,直到实例准备完毕。

语法如下:

<template>
  <div>
    <p v-cloak>{{ text }}</p>
  </div>
</template>

<script>
export default {
  name: "home",
  data() {
    return {
      text: '这是一段text文本'
    }
  }
}
</script>

<style scoped>
[v-cloak] {
  display: none;
}
</style>

1-5. v-once

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

例子如下:

<template>
  <div class="app">
    <h1 v-once>{{ time }}</h1>
  </div>
</template>

<script>
export default {
  name: 'home',
  data(){
    return {
      time: new Date().getTime()
    }
  },
  mounted() {
    setInterval(() => {
      this.time = new Date().getTime()
    }, 1000)
  }
}
</script>

因为h1标签带上了v-once指令,所以只渲染了一次,后续计时器不停修改值,也不会再渲染,显示的时间戳也就始终停在第一次。

1-6. v-for

基于源数据,多次渲染元素或模板块。数据源可以是stringarrayobjectnumberIterable(有迭代接口的值)。一般v-for会搭配内置属性key使用,避免渲染出现问题。这也是一个经典面试题,具体原因可以看:Vue2.0 v-for 中 :key 到底有什么用?Vue中v-for时为什么要绑定key?

注意:在vue2中,如果在template上使用v-for,且绑定了key,可能会报错:“' cannot be keyed. Place the key on real elements instead”。此时,可以把key移到真实节点上,并且修改.eslintrc.js的配置,在rules的规则上添加:'vue/no-v-for-template-key': 'off'

例子如下:

<template>
  <div class="app">
    <!-- 例1 -->
    <h1 v-for="(item, index) in obj" :key="index">{{item}}</h1>
    <!-- 例2 -->
    <h1 v-for="item in 10" :key="item">{{item}}</h1>
    <!-- 例3 -->
    <div v-for="item in arr" :key="item">
      <span>姓名:{{item.name}}</span>
      <span>年龄:{{item.age}}</span>
    </div>
  </div>
</template>

<script>
export default {
  name: 'home',
  data(){
    return {
      obj: {
        a: 100,
        b: 200,
        c: 300
      },
      arr: [
        { name: 'x', age: 10 },
        { name: 'xx', age: 11 },
        { name: 'xxx', age: 12 }
      ]
    }
  }
}
</script>

1-7. v-if和v-else和v-if-else

根据表达式之真假值来渲染元素。在切换时绑定该指令的元素/组件会被销毁并重建。如果是绑定在template标签上,将提出template的内容作为条件块。

注:在vue2中。当 v-if 和 v-for 一起使用时,v-for 的优先级比 v-if 高,而在vue3中则相反。

注:该指令可触发组件过渡效果

例子如下:

<template>
  <div class="app">
    <h1 v-if="num < 2">num小于2</h1>
    <h2 v-else-if="num <= 5">num小于等于5</h2>
    <h3 v-else>num大于5</h3>
  </div>
</template>

<script>
export default {
  name: 'home',
  data(){
    return {
      num: Math.round(Math.random() * 10)
    }
  }
}
</script>

1-8. v-show

根据表达式之真假值,切换元素的CSS display属性,当值为false时,display为none

该指令可触发组件过渡效果

例子如下:

<template>
  <div class="app">
    <h1 v-show="num < 5">我是H1</h1>
  </div>
</template>

<script>
export default {
  name: 'home',
  data(){
    return {
      num: 0
    }
  },
  mounted() {
    setInterval(() => {
      this.num = Math.round(Math.random() * 10)
    }, 1000)
  }
}
</script>

1-9. v-model

在表单控件或者组件上创建双向绑定的值,本质上来说,它是一个语法糖,它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。比如:v-model="foo",等价于 :value="foo"加上@input="foo = $event"的便捷写法。

在大部分情况下,以下两种写法是等价的:

<input v-model="name" />

<input :value="name" @input="name = $event" />

拥有以下修饰符:

  • .lazy:取代 input 监听 change 事件
  • .number:输入字符串转为有效的数字
  • .trim:过滤首尾空格

1-9-1. 表单上使用v-model

<template>
  <div class="app">
    <input type="text" v-model="text">
  </div>
</template>

<script>
export default {
  name: 'home',
  data(){
    return {
      text: ''
    }
  }
}
</script>

1-9-2. 组件上使用v-model

  • 父组件
<template>
  <div>
    <h1>{{ total }}</h1>
    <hello-world v-model="total" @input="handleInput" />
  </div>
</template>

<script>
import helloWorld from '@/components/HelloWorld'

export default {
  name:'home',
  components:{
    helloWorld
  },
  data(){return {
    total: 0
  }},
  methods: {
    handleInput(v) {
      this.total = v
    }
  }
}
</script>
  • 子组件(子组件接收的props值字段必须是value
<template>
  <div class="app">
    <button @click="handleChange">增加</button>
  </div>
</template>

<script>
export default {
  name: 'holle',
  props: ['value'],
  methods: {
    handleChange() {
      this.$emit('input', this.value + 1)
    }
  }
}
</script>

1-9-3. 使用.sync修饰符实现v-model等效

  • 父组件
<template>
  <div>
    <h1>{{ total }}, {{ age }}</h1>
    <hello-world :a.sync="total" :b.sync="age" @input="handleInput" />
  </div>
</template>

<script>
import helloWorld from '@/components/HelloWorld'

export default {
  name:'home',
  components:{
    helloWorld
  },
  data(){return {
    total: 0,
    age: 0
  }},
  methods: {
    handleInput(v1, v2) {
      this.total = v1
      this.age = v2
    }
  }
}
</script>
  • 子组件
<template>
  <div class="app">
    <button @click="handleChange">增加</button>
  </div>
</template>

<script>
export default {
  name: 'holle',
  props: ['a', 'b'],
  methods: {
    handleChange() {
      this.$emit('input', this.a + 1, this.b + 2)
    }
  }
}
</script>

1-10. v-bind

动态的绑定一个或多个属性到组件上,可以绑定class、style等任意值。可以简写为:。拥有以下修饰符:

  • .sync:语法糖,扩展成一个更新父组件绑定值的 v-on 侦听器
  • .camel:kebab-case转camelCase
  • .prop:作为一个DOM property绑定而不是作为attribute绑定,通过自定义属性存储变量,避免暴露数据。防止污染HTML结构

例子:

<template>
  <div>
    <h1 :class="['aaa','bbb', num < 100 ? 'ccc':'ddd']"></h1>
    <h2 :style="mode"></h2>
    <input type="text" :value="msg">
    <img v-bind:src="imageSrc" />
  </div>
</template>

<script>
export default {
  name:'aaa',
  data(){
    return {
      num: 200,
      mode: {
        color: 'red',
        fontSize: '20px'
      },
      msg: '',
      imageSrc: 'https://www.baidu.com/xxx.png'
    }
  }
}
</script>

1-11. v-on

绑定事件监听器。事件类型由参数指定。可以简写为@。拥有以下修饰符:

  • .stop:调用 event.stopPropagation(),阻止冒泡
  • .prevent:调用 event.preventDefault(),阻止元素发生默认的行为
  • .capture:添加事件侦听器时使用 capture 捕获模式
  • .self:事件从侦听器绑定的元素本身触发时才触发回调
  • .keyCode:当事件是从特定按键触发时,才触发回调(注:keyCode的事件用法已经被废弃了并可能不会被最新的浏览器支持
  • .native:监听组件根元素的原生事件
  • .once:只触发一次回调
  • .passive: 滚动事件的默认行为将会立即触发,而不是等到事件触发完再触发,提升移动端的性能
<button v-on:click="add"></button>
// 动态事件
<button @[event]="change"></button>
// 事件对象
<button @click="add('hello', $event)"></button>
// 按键事件
<input @keyup.enter="onEnter" />
// 按键代码
<input @keyup.13="onEnter" />

1-12. v-slot

slot,也称插槽,是组件的一块HTML模版,这块模版由父组件提供。可以说是子组件暴露的一个让父组件传入自定义内容的接口,来实现内容分发。一般都写在template标签上,可以简写为#

1-12-1. 匿名插槽

匿名插槽用slot标签来确定渲染的位置,里面可以放置父组件没传内容时的默认内容,一个组件里,匿名插槽只能有一个。slot="default"可以忽略不写。

  • 父组件
<template>
  <div>
    <aaaChildren></aaaChildren>

    <aaaChildren>
      <h1>我是父组件的内容</h1>
    </aaaChildren>
  </div>
</template>

<script>
import aaaChildren from './children/index.vue'

export default {
  name:'aaa',
  components:{
    aaaChildren
  }
}
</script>
  • 子组件
<template>
  <div class="aaa_children">
    <slot>
      <h1>我是默认内容</h1>
    </slot>
  </div>
</template>

<script>
  export default {
    name:'aaaChildren'
  }
</script>

1-12-2. 具名插槽

slot标签上加入name属性,使得父级可以将内容插入对应的位置中。具名插槽可以有很多个,使用的时候需要带上名字做区分。

  • 父组件
<template>
  <div>
    <aaaChildren>
      <template #text>111</template>
      <template #footer>222</template>
    </aaaChildren>
  </div>
</template>

<script>
import aaaChildren from './children/index.vue'

export default {
  name:'aaa',
  components:{
    aaaChildren
  }
}
</script>
  • 子组件
<template>
	<div class="aaa_children">
		<slot name="text">
			<h1>我是text插槽</h1>
		</slot>
		<div>
			<slot name="footer">
				<h2>我是footer插槽</h2>
			</slot>
		</div>
	</div>
</template>

<script>
export default {
	name: 'aaaChildren'
}
</script>

1-12-3. 作用域插槽

作用域插槽,是在子组件的slot上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件的slot-scope对象上。绑定插槽时用的key,就是父组件获取数据的对象名。如下例子,子组件定义了一个slot,名字为text,并且在slot上绑定了一个data属性,属性的值,就是子组件需要传递过去的值。当父组件使用子组件的text插槽时,通过text插槽获取到scope对象,scope对象上,就可以获取到子组件传递的值。

  • 父组件
<template>
  <div>
    <aaaChildren>
      <template #default="scope">
        <h1>{{ scope.info.age }}</h1>
      </template>
      <template #text="scope">
        <h2>{{ scope.data }}</h2>
      </template>
    </aaaChildren>
  </div>
</template>

<script>
import aaaChildren from './children/index.vue'

export default {
  name:'aaa',
  components:{
    aaaChildren
  }
}
</script>
  • 子组件
<template>
  <div class="aaa_children">
    <slot :info="user"></slot>
    <slot name="text" :data="name"></slot>
  </div>
</template>

<script>
  export default {
    name:'aaaChildren',
    props:[],
    components:{},
    data(){
      return {
        user: {
          age: 18,
          name: '小明'
        },
        name: '卡卡罗特'
      }
    }
  }
</script>

2. 自定义指令

自定义指令。是Vue暴露了自定义指令的API给我们,让我们除了使用内置指令外,我们还可以自己定义指令(比如:复制粘贴指令,图片懒加载指令,防抖指令等等),定义好后和内置指令的方式非常类似。它可以有全局的和局部的两种。指令也有自己的钩子函数,如下:

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

以上钩子函数都有4个参数,如下:

  • el:指令所绑定的元素,可以用来直接操作 DOM
  • binding:一个对象,包含这些属性
    • name:指令名,不包括 v- 前缀
    • value:指令的绑定值,例如:v-bold="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用
    • expression:字符串形式的指令表达式。例如 v-bold="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-bold:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-bold.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用

2-1. 全局自定义指令

通过Vue实例身上的directive() API注册一个全局自定义指令。例子如下:

  • 首先在src文件夹下创建一个directive文件夹,里面创建一个index.js文件,写入以下代码
/**
 * 安装指令
 * @param Vue vue实例
 */
export default function installDirective(Vue) {
  // 粗体指令
	Vue.directive('bold', {
    inserted(el) {
      el.style.fontWeight = 'bold'
    }
  })
}
  • 然后在main.js里,引入directive,然后使用Vue.use安装指令
import Vue from 'vue'
import App from './App.vue'
import initDirective from '@/directive'

Vue.use(initDirective)

new Vue({
  render: h => h(App),
}).$mount('#app')
  • 使用如下:
<template>
  <div v-bold>123</div>
</template>

2-2. 局部自定义指令

组件中也接受一个directives的选项,在组件中定义的自定义指令,就是局部自定义指令,其参数和全局指令一致。例子如下:

<template>
	<div v-bold>123</div>
</template>

<script>
export default {
	name: 'aaaChildren',
	directives: {
		bold: {
			inserted: function (el) {
				el.style.fontWeight = 'bold'
			}
		}
	}
}
</script>

2-3. 动态参数和简写

指令的参数可以是动态,这样可以灵活运用,例子如下,可以动态修改高度的指令:

  • 注册指令:
/**
 * 安装指令
 * @param Vue vue实例
 */
export default function initDirective(Vue) {
  // 简写 在bind和update时触发相同行为
  Vue.directive('height', (el, binding) => {
    el.style.backgroundColor = 'skyblue'
    el.style.height = `${binding.value}px`
  })
}
  • 使用指令:
<template>
  <div>
    <button @click="addHeight">点击增高</button>
    <div v-height="h"></div>
  </div>
</template>

<script>
export default {
  name:'aaa',
  data(){
    return {
      h: 100
    }
  },
  methods: {
    addHeight() {
      this.h += 2
    }
  }
}
</script>