Vue.js指令 (Directives) 是带有
v-
前缀的特殊特性。指令特性的值预期是单个 JavaScript 表达式 (v-for 是例外情况)。我们可以将指令看作特殊的HTML特性,作用是:当表达式的值改变时,将其产生的连带影响,响应式地作用于绑定的DOM。
内置指令指的就是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
该指令的作用是更新DOM元素的文本内容,使用率很低,一般都是用双花括号来代替了,可以忽略
语法如下:
<template>
<div>
<span v-text="msg"></span>
</div>
</template>
<script>
export default {
name: "home",
data() {
return {
msg: '我是一个文本信息'
}
}
}
</script>
该指令的作用是更新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>
该指令不需要表达式,作用是跳过绑定了这个指令的元素和它的子元素的编译过程。跳过大量没有指令的节点会加快编译,但是绑定了后,会直接显示双花括号{{ xxx }}
,按需使用。
该指令不需要表达式。这个指令会保持在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>
该指令不需要表达式,作用是只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
例子如下:
<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
指令,所以只渲染了一次,后续计时器不停修改值,也不会再渲染,显示的时间戳也就始终停在第一次。
基于源数据,多次渲染元素或模板块。数据源可以是string
、array
、object
、number
、Iterable(有迭代接口的值)
。一般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>根据表达式之真假值来渲染元素。在切换时绑定该指令的元素/组件会被销毁并重建。如果是绑定在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>根据表达式之真假值,切换元素的
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>在表单控件或者组件上创建双向绑定的值,本质上来说,它是一个语法糖,它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。比如:
v-model="foo"
,等价于:value="foo"
加上@input="foo = $event"
的便捷写法。在大部分情况下,以下两种写法是等价的:
<input v-model="name" /> <input :value="name" @input="name = $event" />拥有以下修饰符:
- .lazy:取代 input 监听 change 事件
- .number:输入字符串转为有效的数字
- .trim:过滤首尾空格
<template> <div class="app"> <input type="text" v-model="text"> </div> </template> <script> export default { name: 'home', data(){ return { text: '' } } } </script>
- 父组件
<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>
- 父组件
<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>动态的绑定一个或多个属性到组件上,可以绑定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>绑定事件监听器。事件类型由参数指定。可以简写为
@
。拥有以下修饰符:
- .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" />slot,也称插槽,是组件的一块HTML模版,这块模版由父组件提供。可以说是子组件暴露的一个让父组件传入自定义内容的接口,来实现内容分发。一般都写在
template
标签上,可以简写为#
。匿名插槽用
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>在
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>作用域插槽,是在子组件的
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>自定义指令。是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 钩子中可用
通过
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>组件中也接受一个
directives
的选项,在组件中定义的自定义指令,就是局部自定义指令,其参数和全局指令一致。例子如下:<template> <div v-bold>123</div> </template> <script> export default { name: 'aaaChildren', directives: { bold: { inserted: function (el) { el.style.fontWeight = 'bold' } } } } </script>指令的参数可以是动态,这样可以灵活运用,例子如下,可以动态修改高度的指令:
- 注册指令:
/** * 安装指令 * @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>