Vue-指令

指令

内置指令

v-show

根据表达式值的真假,显示或隐藏HTML元素。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>v-show指令</title>
	</head>
	<body>
		<div id="app">
			<h1 v-show="yes">Yes!</h1>
			<h1 v-show="no">No!</h1>
			<h1 v-show="age >= 25">Age: {{ age }}</h1>
			<h1 v-show="name.indexOf('Smith') >= 0">Name: {{ name }}</h1>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
			const vm = Vue.createApp({
				data() {
				    return {
				        yes: true,
					    no: false,
					    age: 28,
					    name: 'Will Smith'
				    }
				}
			}).mount('#app');
		</script>
	</body>
</html>

在浏览器的Elements窗口可看出使用v-show指令,元素本身要被渲染display属性控制。

v-if/v-else-if/v-else

v-if指令根据表达式值的真假来生成或删除一个元素。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>v-if指令</title>
	</head>
	<body>
		<div id="app">
			<h1 v-if="yes">Yes!</h1>
			<h1 v-if="no">No!</h1>
			<h1 v-if="age >= 25">Age: {{ age }}</h1>
			<h1 v-if="name.indexOf('Smith') >= 0">Name: {{ name }}</h1>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
			const vm = Vue.createApp({
				data(){
				    return {
				        yes: true,
					    no: false,
					    age: 28,
					    name: 'Will Smith'
				    }
				}
			}).mount('#app');
		</script>
	</body>
</html>

v-if指令与v-show不同,当表达式的值为false时,v-if指令不会创建该元素,只有当为true时,v-if指令才会创建该元素;v-show不管表达式的值是真是假都会创建,用display控制显示与否。

如果需要控制多个元素的创建或删除,可以用template元素包裹这些元素:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>v-if指令</title>
	</head>
	<body>
		<div id="app">
			<template v-if="!isLogin">
				<form>
					<p>username: <input type="text"></p>
					<p>password: <input type="password"></p>
				</form>
			</template>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
			const vm = Vue.createApp({
				data() {
				    return {
				        isLogin: false
				    }
				}
			}).mount('#app');
		</script>
	</body>
</html>

v-else-if/v-else

两者一起使用可以实现互斥的条件判断。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<div id="app">
	    <span v-if="score >= 85">优秀</span>
	    <span v-else-if="score >= 75">良好</span>
	    <span v-else-if="score >= 60">及格</span>
	    <span v-else>不及格</div>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
			const vm = Vue.createApp({
			    data() {
			        return {
			            score: 90
			        }
			    }
			}).mount('#app');
		</script>
	</body>
</html>

v-for

通过循环的方式渲染一个列表,循环的对象可以是数组,也可以是一个JavaScript对象。

v-for遍历数组

表达式语法形式为item in items。

<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			<ul>
				<li v-for="(book,index) in books">{{index}} - {{book.title}}</li>
			</ul>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
			const vm = Vue.createApp({
				data() {
				    return {
				        books: [
    						{title: 'Java无难事'},
    						{title: 'VC++深入详解'},
    						{title: 'Servlet/JSP深入详解'}
    					]
				    }
				}
			}).mount('#app');
		</script>
	</body>
</html>

v-for支持一个可选的参数作为当前项的索引。多个参数需要放到圆括号中。

数组更新检测

Vue的核心是数据与视图的双向绑定,为了监测数组中元素的变化,以便能及时将变化反映到视图,vue对数组的下列变异方法进行了包裹。push(),pop(),shift(),unshift(),splice(),sort(),reverse()。

打开上面的网页,在Console输入下面的语句:vm.books.push({title:'zzd'})可以添加元素。

数组中还有一些非变异方法,如filter()、concat()、slice(),它们不会改变原始数组,而总是返回一个新数组。对于这些方法,要想让vue自动更新视图,可以使用新数组替换原来的数组。

输入:vm.books = vm.books.concat([{title:'zzd2'},{title:'zzd3'}])。

vue在检测到数组变化时并不是直接重新渲染整个列表,而是最大化地复用DOM元素。在替换的数组中,含有相同元素的项不会被重新渲染,因此可以大胆地使用新数组替换旧数组,不用担心性能问题。

过滤与排序

有时想要显示一个数组经过过滤或排序后的版本,但不实际改变或重置原始数据。在这种情况下可以创建一个计算属性,来返回过滤或排序后的数组:

<li v-for='n in evenNumbers'>{{ n }}</li>

data() {
    return {
        numbers:[1,2,3,4,5]
    }
},
    computed:{
        evenNumbers(){
            return this.numbers.filter(function(number){
                return number % 2 ===0
            })
        }
    }

在计算属性不适用的情况下(如在嵌套v-for循环中),也可以使用一个方法。

<li v-for='n in evenNumbers'>{{ n }}</li>

data() {
    return {
        numbers:[1,2,3,4,5]
    }
},
    methods:{
        even:function(numbers){
            return numbers.filter(function(number){
                return number % 2===0
            })
        }
    }

遍历整数

v-for可以接受整数,会把模板重复对应次数:

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

遍历对象

<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <div id="app">
            <ul>
                <li v-for="(value, key, index) in book">{{index}}. {{key}} : {{value}}</li>
            </ul>
        </div>
    
    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const vm = Vue.createApp({
            data() {
                return {
                    book: {
                        title: 'VC++深入详解',
                        author: '孙鑫',
                        isbn: '9787121362217'
                    }
                }
            }
        }).mount('#app');
    </script>
  </body>
</html>

在template上使用v-for

<ul>
    <template v-for='item in items'>
    <li>{{ item.msg }}</li>
    <li>{{ item.code }}</li>
    </template>
</ul>

key属性

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <div id="app">
            <p>
                ID:<input type="text" v-model="bookId"/>
                书名:<input type="text" v-model="title"/>
                <button v-on:click="add()">添加</button>
            </p>
            <p v-for="book in books">
                <input type="checkbox">
                <span>ID:{{book.id}} , 书名:{{book.title}}</span>
            </p>
        </div>
    
    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const vm = Vue.createApp({
            data() {
                return {
                    bookId: '',
                    title: '',
                    books: [
                        {id: 1 ,title: 'Java无难事'},
                        {id: 2, title: 'VC++深入详解'},
                        {id: 3, title: 'Servlet/JSP深入详解'}
                    ]
                }
            },
            methods:{
                add(){
                  this.books.unshift({
                      id : this.bookId,
                      title : this.title
                  });
                  this.bookId = '';
                  this.title = '';
                }
            }
        }).mount('#app');
    </script>
  </body>
</html>

这段代码预先定义了一个books数组对象,通过v-for指令遍历该数组,同时提供了两个输入框,在用户输入图书的ID和书名后,向数组中添加一个新的图书对象。我们使用的是数组的unshift方法,该方法向数组的开头添加一个或多个元素。

浏览器打开页面先选中第一项,然后输入新的图书ID和书名,添加。会发现勾选的会从第一个变成新添加的图书对象。产生的问题原因是:当Vue正在更新使用v-for渲染的元素列表时,它默认使用就地修补策略。如果数据项的顺序改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染,在本例中,当勾选“Java无难事”时,指令就记住了勾选的数组下标为0,当向数组中添加新的元素后,虽然数组长度变化了,但是指令只记得当时勾选的数组下标,于是就把新数组中下标为0的“Java Web开发详解”勾选了。

为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,需要为列表的每一项提供一个唯一key属性。key属性的类型只能是string或number类型。

修改上面的代码,在v-for指令后添加key属性:

<p v-for='book in books' v-bind:key='book.id'>

v-for与v-if一同使用

如果在渲染一个列表时对列表中的某些项需要根据条件判断是否渲染,那么就可以将v-if和·v-for联合使用。不过两者在同一元素上使用时,v-if的优先级更高,这意味着v-if条件不能访问v-for范围内的变量。例如下面的代码将抛出错误,提示isComplete属性没有定义。

<li v-for='plan in plans' v-if='plan.isComplete'>{{ plan.content }}</li>

要解决这个问题可以在li元素外部用template元素包裹:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <div id="app">
            <h1>已完成的工作计划</h1>
            <ul>
                <template v-for="plan in plans">
                    <li v-if="plan.isComplete">
                        {{plan.content}}
                    </li>
                </template>
                
            </ul>
            <h1>未完成的工作计划</h1>
            <ul>
                <template v-for="plan in plans">
                    <li v-if="!plan.isComplete">
                        {{plan.content}}
                    </li>
                </template>
            </ul>
        </div>
    
    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const vm = Vue.createApp({
            data() {
                return {
                    plans: [
                        {content: '写《Java无难事》', isComplete: false},
                        {content: '买菜', isComplete: true},
                        {content: '写PPT', isComplete: false},
                        {content: '做饭', isComplete: true},
                        {content: '打羽毛球', isComplete: false}
                    ]
                }
            }
        }).mount('#app');
    </script>
  </body>
</html>

Vue不建议同时使用v-if和v-for指令,尽量使用计算属性代替。

v-bind

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <div id="app">
            <!-- 绑定一个属性 -->
            <img v-bind:src="imgSrc">
            
            <!-- 简写语法 -->
            <img :src="imgSrc">
            
            <!-- 动态属性名 -->
            <a v-bind:[attrname]="url">链接</a>
            
            <!-- 内联字符串拼接 -->
            <img :src="'images/' + fileName">
            
            <!-- 绑定一个有属性的对象 -->
			<form v-bind="formObj">
				<input type="text">
			</form>
        </div>
    
    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const vm = Vue.createApp({
            data() {
                return {
                    attrname: 'href',
                    url: 'http://www.sina.com.cn/',
                    imgSrc: 'images/bg.jpg',
                    fileName: 'bg.jpg',
                    formObj: {
						method: 'get',
						action: '#'
					}
                }
            }
        }).mount('#app');
    </script>
  </body>
</html>

v-bind指令还可以直接绑定一个包含属性名-值对的对象,此时v-bind指令不需要接参数,直接使用即可:

<div id="app">
      <!-- 绑定一个有属性的对象 -->
     <form v-bind='formObj'>
         <input type='text'>
    </form>     
</div>
<script>
        const vm = Vue.createApp({
            data() {
                return {
                   formObj:{
                       method:'get',
                       action:'#'
                   }
					}
                }
            }
        }).mount('#app');
    </script>

v-model

用来在表单input、textarea、select元素上创建双向绑定,它会根据控件类型自动选取正确的方法来更新元素。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <div id="app">
            <input type="text" v-model="message">
        </div>
    
    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const vm = Vue.createApp({
            data() {
                return {
                    message: 'Hello World'    
                }
            }
        }).mount('#app');
    </script>
  </body>
</html>

窗口输入:vm.message = 'welcome'。

可以看到控件中的内容发生了变化。再在控件中随便输入内容,在窗口用vm.message输入也会变化。

v-on

用于监听事件,并在触发时运行一些JavaScript代码。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <div id="app">
            <p>
                <!--① click事件直接使用JavaScript语句-->
                <button v-on:click="count += 1">Add 1</button>
                <span>count: {{count}}</span>
            </p>
            <p>
                <!--② click事件直接绑定一个方法-->
                <button v-on:click="greet">Greet</button>
                <!--简写语法-->
                <button @click="greet">Greet</button>
            </p>
            <p>
                <!--③ click事件使用内联语句调用方法-->
                <button v-on:click="say('Hi')">Hi</button>
            </p>
        </div>
    
    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const vm = Vue.createApp({
            data() {
                return {
                    count: 0,
                    message: 'Hello, Vue.js!'
                }
            },
            //在选项对象的methods属性对象中定义方法
            methods: {
                greet: function() {
                    //方法的this值始终指向组件实例
                    alert(this.message)
                },
                //对象方法的简写语法
                say(msg) {
                    alert(msg)
                }
            }
        }).mount('#app');
    </script>
  </body>
</html>

如果在事件绑定的方法中需要访问原始的DOM事件,可以使用特殊的变量$event把它传入方法。在单击链接的时候会跳转到链接指向的页面,然而有时候1会根据某个条件是否达成来决定是否跳转,如果条件不满足这时会调用事件对象的preventDefault方法阻止跳转。在使用v-on指令绑定事件处理器时,就可以使用$event传入原始的DOM事件对象,然后在事件处理器方法中访问原生的事件对象:

<a href='/login' v-on:click='login($event)'>登录</a>
//...
methods:{
login(event){
	//...
	if(event)
		event.preventDefault();
}
}

上述代码也可以使用事件修饰符.prevent实现。

此外,在事件方法中如果要访问事件绑定的原始DOM元素节点对象,可以调用event.currentTarget得到。

在使用v-on指令绑定的事件处理器中可以有多个方法,方法之间用逗号运算符分割:

<!-- 当单击按钮时,one()和two()方法都将被执行 -->
<button @click='one($event),two($event)'>
    Submit
</button>
//...
methods:{
one(event){
	//第一个处理逻辑
	},
two($event){
	//第二个处理逻辑
	}
}

事件修饰符

在事件处理程序中调用event.preventDefault()方法或event.stopPropagation()方法是非常常见的需求,为了解决这个问题,vue提供了事件修饰符,是由圆点开头的指令,紧跟在事件名称后面书写。

针对v-on,有以下修饰符:

  • .stop:调用event.stopPropagation()。
  • .prevent:调用event.preventDefault()。
  • .capture:添加事件监听器时使用capture模式。
  • .self:仅当事件从监听器绑定的元素本身触发时才触发回调。
  • .{keyAlias}:仅当事件是从特定按键触发时才触发回调。
  • .once:仅触发一次回调。
  • .left:仅当鼠标左键时触发。
  • .right:仅当鼠标右键时触发。
  • .middle:仅当鼠标中键时触发。
  • .passive:以{passive:true}模式添加侦听器。

针对前面调用event.preventDefault()方法阻止默认的链接跳转行为的需求。使用事件修饰符就可以轻松实现:

<a href='/login' v-on:click.prevent='login'>登录</a>
//...
methods:{
	login(){
	//....
}
}

下面是上述部分事件修饰符的用法:

<!--阻止单击事件继续传播-->
<a v-on:click.stop='doThis'></a>
<!--提交事件不再重新加载页面-->
<form v-on:submit.prevent='onSubmit'></form>
<!--修饰符可以串联-->
<a v-on:click.stop.prevent='doThat'></a>
<!--只有修饰符-->
<form v-on:submit.prevent></form>

<!--添加事件监听器时使用事件捕获模式-->
<!--即内部元素触发的事件先在该事件处理函数中处理,然后交由内部元素处理-->
<div v-on:click.capture='doThis'>...</div>

<!--只当在event.target是当前元素自身时触发处理函数-->
<div v-on:click.self='doThat'>...</div>
<!--单击事件处理函数只执行一次-->
<a v-on:click.once='doThis'></a>

<!--对象语法-->
<button v-on='{mousedown:doThis,mouseup:doThat}'>
</button>

说明:

DOM事件规范支持两种事件模型,即捕获型事件和冒泡型事件,捕获型事件从最外层的对象(大部分浏览器是window)开始,直到引发事件的对象;冒泡型事件从引发事件的对象开始一直向上传播,直到最外层的对象结束。任何发生在DOM事件模型中的事件首先进入捕获阶段,直到达到目标对象,再进入冒泡阶段。v-on指令提供的.stop和.capture修饰符即与此有关。

修饰符可以串联使用,但顺序很重要。例如v-on:click.prevent.self会阻止所有的单击,而v-on:click:self.prevent只会阻止对元素自身的单击。

按键修饰符

<!-- 只有在按键是回车键时调用submit方法-->
<input v-on:keyup.enter='submit'>

常用的按键别名:.enter、tab、delete、esc、space、up、down、left、right

系统修饰键

.ctrl、.alt、.shift、.meta

<input @keyup.alt.enter='clear'>
<div @click.ctrl='dosomething'>
    ...
</div>

.exact修饰符

用于精确控制系统修饰符组合触发的事件。

<!--即使同时按下ALt或shift键才会触发-->
<button @click.ctrl='onClick'>A</button>
<!--只有在按下ctrl键而不按其他键才会触发-->
<button @click.ctrl.exact='onCtrlClick'>A</button>
<!--只有没有按下系统修饰键时才会触发-->
<button @click.exact='onClick'>A</button>

鼠标按钮修饰符

.left、.right、.middle

<!--只有在按下鼠标右键才会触发事件处理函数-->
<input @click.right='somrthing'>

v-text

用于更新元素的文本内容(textContent属性)。

<span v-text='message'></span>
<!--等价于
<span v-text>{{message}}</span> -->
<script>
const vm = Vue.createApp({
    data(){
        return{
            message:'hello vue'
        }
    }
}).mount('#app');
</script>

渲染结果如下:

<span>hello vue</span>

v-html

用于更新元素的innerHTML,该部分作为普通HTML代码插入,不作为Vue模板进行编译。

<div v-html='hElt'></div>
<script>
	const vm = Vue.createApp({
        data(){
            return{
                hElt:'<h1>hhh</h1>'
            }
        }
    }).mount('#app');
</script>

v-once

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

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		<style>
			a {
				margin: 20px;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<h1>{{title}}</h1>
			<a v-for="nav in navs" :href="nav.url" v-once>{{nav.name}}</a>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
			const vm = Vue.createApp({
				data() {
				    return {
				        title: 'v-once指令的用法',
    					navs: [
    						{name: '首页', url: '/home'},
    						{name: '新闻', url: '/news'},
    						{name: '视频', url: '/video'},
    					]
				    }
				}
			}).mount('#app');
		</script>
	</body>
</html>

v-once指令在首次渲染时是看不出有什么不同的,下面到Console窗口输入:vm.navs.push({name:'论坛',url:'/bbs'}),我们会发现页面没有任何变化,这是v-once的作用,只渲染一次,结果在之后作为静态内容存在。

v-pre

也不需要表达式,用于跳过这个元素及其所有子元素的编译。v-pre指令可用于显示原始花括号标签。对于大量没有指令的节点使用v-pre可以加快编译速度。

<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			<h1 v-pre>{{message}}</h1>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
			const vm = Vue.createApp({
				data() {
				    return {
				        message: 'Java',    
				    }
				}
			}).mount('#app');
		</script>
	</body>
</html>

渲染结果为:{{message}}

v-cloak

不需要表达式,这个指令保留在元素上直到关联的组件实例编译结束,编译结束后该指令也被删除。当和css规则(如[v-cloak]{display:none})一起使用时,这个指令可以隐藏未编译的花括号标签直到组件实例准备完毕。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		<style>
			[v-cloak] {
				display: none;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<h1 v-cloak>{{message}}</h1>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
			const vm = Vue.createApp({
				data() {
				    return {
				        message: 'Vue.js无难事'
				    }
				}
			}).mount('#app');
		</script>
	</body>
</html>

浏览器在加载页面时,如果网速较慢或页面较大,那么浏览器在构造完DOM树后会在页面中直接显示{{message}}字样,直到Vue的JS文件加载完毕,组件实例创建、模板编译后,{{message}}才会被替换为数据对象的内容。在这个过程中页面还是会有闪烁的,这给用户体验不是很好。如果加上一条css规则:[v-cloak] {display: none;},配合v-cloak一起使用就可以解决这个问题。

v-slot

用于提供命名的插槽或需要接收prop的插槽。

自定义指令

自定义指令的注册

注册后才能使用。有全局注册和本地注册。全局注册使用应用程序的directive()方法注册一个全局自定义指令,该方法接受两个参数,第一个参数是指令的名字;第二个参数是一个定义对象或函数对象,将指令要实现的功能在这个对象中定义。语法形式如下:app.directive(name,[definition])。

例如要编写一个让元素自动获取焦点的全局指令:

const app = Vue.createApp({});
app.directive('focus',{...})

本地注册是在组件实例的选项对象中使用directives选项进行注册:

directives:{
    focus:{
        mounted(el){
            el.focus()
        }
    }
}

然后在模板中就可以在任何元素上使用v-focus指令了。

钩子函数

自定义指令的功能是在定义对象中实现的,而定义对象则是由钩子函数组成的,Vue提供了下面几个钩子函数,这些钩子函数都是可选的。

  • beforeMount:当指令第一次绑定到元素并且挂载父组件之前调用。指令如果需要一些一次性的初始化设置,可以放到这个钩子函数里。
  • mounted:在挂载绑定元素的父组件时调用。
  • beforeUpdate:指令所在组件的VNode更新之前调用。
  • updated:指令所在组件的VNode及其子组件的VNode全部更新后调用。
  • beforeUnmount:在写在绑定元素的父组件之前调用。
  • unmounted:只调用一次,指令与元素解绑且父组件已卸载时调用。

在绑定元素被挂载到DOM时自动获取焦点可以用mounted钩子函数,在里面编写自动聚焦的代码:

<div id='app'>
    <input v-focus>
</div>
<script>
	const app = Vue.createApp({});
    app.directive('focus',{
        //当绑定元素被挂载到DOM中时
        mounted(el){
            //聚焦元素
            el.focus()
        }
    }).mount('#app')
</script>

指令的钩子函数可以带一些参数:

  • el:指令所绑定的元素,可以用来直接操作DOM。
  • binding:一个对象,包含以下属性:
    • instance:使用指令的组件的实例。
    • value:传给指令的值。
    • oldValue:前一个值,仅在beforeUpdate和updated钩子中可用。
    • arg:传给指令的参数,可选。例如在v-my-directive:foo中,arg的值为foo
    • modifiers:一个包含修饰符的对象。例如,在v-my-directive:foo.bar中,modifiers对象的值为{foo:true,bar:true}。
    • dir:注册指令时作为参数传递的对象。
  • vnode:Vue编译生成的虚拟节点。
  • oldVnode:上一个虚拟节点,仅在beforeUpdate和updated钩子中可用。

需要注意的是,除了el参数外,其他参数都应该是只读的,切勿进行修改。

下面编写一个自定义指令,在其钩子函数中将各个参数输出:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		
	</head>
	<body>
		<div id="app">
		    <p v-demo:foo.a.b="message"></p>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
		    const app = Vue.createApp({
		        data() {
    			    return {
    			        message: 'Java无难事'
    			    }
			    }
		    })
		    
			app.directive('demo', {
			    mounted (el, binding, vnode) {
    			    let s = JSON.stringify
    			    el.innerHTML =
    			      'instance: '   + s(binding.instance) + '<br>' +
    			      'value: '      + s(binding.value) + '<br>' +
    			      'argument: '   + s(binding.arg) + '<br>' +
    			      'modifiers: '  + s(binding.modifiers) + '<br>' +
    			      'vnode keys: ' + Object.keys(vnode).join(', ')
    			    
			    }
			})
			app.mount('#app')
		</script>
	</body>
</html>

将bind钩子函数的所有参数信息取出来拼接成字符串,赋值给div元素的innerHTML属性。

instance: {"message":"Java无难事"}
value: "Java无难事"
argument: "foo"
modifiers: {"a":true,"b":true}
vnode keys: __v_isVNode, __v_skip, type, props, key, ref, scopeId, slotScopeIds, children, component, suspense, ssContent, ssFallback, dirs, transition, el, anchor, target, targetAnchor, staticCount, shapeFlag, patchFlag, dynamicProps, dynamicChildren, appContext

动态指令参数

自定义指令也可使用动态参数。例如v-mydirective:[argument]='value'中,argument参数可以根据组件实例数据进行更新。

例如我们想让某个元素固定在页面中的某个位置,在出现滚动条时,元素也不会随着滚动。这可以通过设置css样式属性position为fixed实现,同时使用top、right、bottom、left等属性以窗口为参考点进行定位。下面使用自定义指令来实现这个功能:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		
	</head>
	<body>
		<div id="app">
			<!--直接给出指令的参数-->
			<p v-pin:top="100">
				Java无难事
			</p>
			<!--使用动态参数-->
			<p v-pin:[direction]="100">
				Java无难事
			</p>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
		    const app = Vue.createApp({
		        data() {
    			    return {
    			        direction: 'left'
    			    }
			    }
		    })
			app.directive('pin', {
			  beforeMount(el, binding, vnode) {
			  	el.style.position = 'fixed';
			  	let s = binding.arg || 'left';
	    		el.style[s] = binding.value + 'px'
			  }
			})
			app.mount('#app')
		</script>
	</body>
</html>

函数简写

如果自定义指令在mounted和updated钩子函数中的行为一致,且只需要用到这两个钩子函数,那么可以在注册时传递一个函数对象作为参数:

app.directive('color-swatch',(el,binding) => {
    el.style.backgroundColor = binding.value
})

对象字面量

如果指令需要多个值,可以传入一个JavaScript对象字面量。

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

实例

通过指令实现下拉菜单

下拉菜单通常用a标签定义。在页面中编写菜单时,一种方式是将所有的菜单和子菜单项硬编码实现,就是一堆a标签堆在一起;另一种方式是把菜单和子菜单按照层级关系定义为一个大的JavaScript对象,然后通过脚本动态呈现。本案例用第二种方式,然后按照菜单的层级关系通过嵌套的v-for指令循环输出。

在Vue实例的数据属性中定义一个menus数组,将各个顶层菜单定义为一个对象,作为数组中的元素,子元素作为顶层菜单对象的属性嵌套定义:

data:{
    menus:[
        {
            name:'我的淘宝',url:'#',show:false,subMenus:[
                {name:'已买到的宝贝',url:'#'},
                {name:'已卖出的宝贝',url:'#'},
            ]
        },
        {
            name:'收藏夹',url:'#',show:false,subMenus:[
                {name:'收藏的宝贝',url:'#'},
                {name:'收藏的店铺',url:'#'},
            ]
        }
    ]
}

我们为每一个顶层菜单对象定义了一个show属性,初始值为false,该属性主要用于控制其下的子菜单是否显示。当鼠标移动到菜单列表上显示。接下来使用v-for循环输出菜单:

<div id='app' v-cloak>
  <li v-for='menu in menus' @mouseover='menu.show = !menu.show' @mouseout='menu.show = !menu.show'>
      <!--鼠标放上去mouseover将show取反,变为true;mouseout又取反变回false。-->
    <a :href='menu.url'>{{menu.name}}</a>
    <ul v-show='menu.show'>
        <li v-for='subMenu in menu.subMenus'>
        	<a :href='subMenu.url'>{{subMenu.name}}</a>
        </li>
    </ul>
  </li>
</div>

说明:

  • 在div元素中使用v-cloak指令避免页面加载时的闪烁问题,要和[v-cloak]{display:none}一起使用。
  • 绑定mouseover、mouseout事件采用了v-on指令的简写语法,menu.show初始为false,因此@mouseover的表达式计算结果是将menu.show设为true,而@mouseout表达式的结果是将menu.show设为false。
  • 子菜单放在ul元素内,在该元素上使用v-show指令根据表达式menu.show的值动态控制子菜单的隐藏和显示。这里不适合用v-if,因为子菜单的显示和隐藏可能会频繁切换。

完整代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		<style>
			body {
				width: 600px;
			}
			a {
				text-decoration: none;
				display: block;
				color: #fff;
				width: 120px;
				height: 40px;
				line-height: 40px;
				border: 1px solid #fff;
				border-width: 1px 1px 0 0;
				background: #255f9e;
			}
			li {
				list-style-type: none;
			}
			#app > li {
				list-style-type: none;
				float: left;
				text-align: center;
				position: relative;
			}
			#app li a:hover {
				color: #fff;
				background: #ffb100;
			}
			#app li ul {
				position: absolute;
				left: -40px;
				top: 40px;
				margin-top: 1px;
				font-size: 12px;
			}
			[v-cloak] {
				display: none;
			}
		</style>
	</head>
	<body>
		<div id = "app" v-cloak>
			<li v-for="menu in menus" @mouseover="menu.show = !menu.show" @mouseout="menu.show = !menu.show">
				<a :href="menu.url" >
					{{menu.name}}
				</a>
				<ul v-show="menu.show">
					<li v-for="subMenu in menu.subMenus">
						<a :href="subMenu.url">{{subMenu.name}}</a>
					</li>
				</ul>
			</li>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
		    const data = {
		      menus: [
		      	{
		      		name: '我的淘宝', url: '#', show: false, subMenus: [
		      				{name: '已买到的宝贝', url: '#'},
		      				{name: '已卖出的宝贝', url: '#'}
		      			]
		      	},
		      	{
		      		name: '收藏夹', url: '#', show: false, subMenus: [
		      				{name: '收藏的宝贝', url: '#'},
		      				{name: '收藏的店铺', url: '#'}
		      			]
		      	}
		      ]
		    };
		    const vm = Vue.createApp({
                data() {
                    return data;
                }
            }).mount('#app');
		</script>
	</body>
</html>

使用自定义指令实现随机背景色

有时候会使用一幅图片作为网页中某个元素的背景图,当网络状况不好时或者图片较大时加载会很慢,可以先在该元素的区域用随机的背景色填充,等图片加载完成后再把元素的背景替换为图片。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		
		<style>
			div{
				width: 567px; 
				height: 567px;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<div v-img="'images/bg.jpg'"></div>
		</div>
	
		<script src="https://unpkg.com/vue@next"></script>
		<script>
		    const app = Vue.createApp({});
			app.directive('img', {
				mounted: function(el, binding){
					let color = Math.floor(Math.random() * 1000000);
					el.style.backgroundColor = '#' + color;
					let img = new Image();
					img.src = binding.value;
					img.onload = function(){
						el.style.backgroundImage = 'url(' + binding.value + ')';
					}
				}
			})
			app.mount('#app');
		</script>
	</body>
</html>
posted @ 2021-08-11 22:02  KKKyrie  阅读(61)  评论(0编辑  收藏  举报