第八章 自定义指令
8.1 基本用法
自定义指令的注册方法和注册组件很像,也分全局注册和局部注册。如注册一个v-focus指令:
<script type="text/javascript"> //全局注册 Vue.directive('focus',{ //指令选项 }); //局部注册 var app=new Vue({ el:"#app", directives:{ focus:{ //指令选项 } } }) </script>
8.1.1 钩子函数组成
自定义指令由几个可选钩子函数组成。
-
- bind:只调用一次,指令第一次绑定到元素时调用,可以定义一个在绑定时执行一次的初始化动作。
- inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document中)。
- update:被绑定元素所在的模板更新时调用,无论绑定值是否有变化。通过比较更新前后的绑定值来忽略不必要的模板更新。
- componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
- unbind:只调用一次,指令与元素解绑时调用。
如上面的v-focus指令,希望在元素插入父节点时获取到焦点:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>8.1 基本用法</title> </head> <body> <div id="app"> <input type="text" v-focus> </div> </body> <script src="vue.js"></script> <script type="text/javascript"> //全局注册 Vue.directive('focus',{ //指令选项 inserted:function (el) { el.focus(); } }); //局部注册 var app=new Vue({ el:"#app" }); </script> </html>
页面刷新即可获取到焦点
8.1.2 钩子函数参数
钩子函数可选参数:
-
- el:指令所绑定的元素,可以用来直接操作DOM
- binding:一个对象,包含如下属性:
- name:指令名称,不包括前缀v-
- value:指令绑定值,如v-my-directive="1+1",则value值为2
- oldValue:指令绑定的前一个值,仅在update和componentUpdate钩子中可用,无论值是否改变都可以用。
- expression:绑定值的字符串形式。如v-my-directive="1+1",expression值是“1+1”,而不是2
- arg:传给指令的参数。如v-my-directive:foo,arg的值为foo
- modifiers:一个包含修饰符的对象。如v-my-directive.foo.bar,修饰符对象的值是{foo.true,bar.true}
- vnode:Vue编译生成虚拟节点。
- oldVnode:上一个虚拟节点,仅在update和componentUpdate钩子中可用。
8.2 实战
8.2.1 开发一个可从外部关闭的下拉菜单
知识点:contains 方法是用来判断元素A 是否包含了元素B ,包含返回true,不包含返回false.
index.js
var app=new Vue({ el:"#app", data:{ show:false, }, methods:{ handleClose:function () { this.show=false; } } });
clickoutside.js
//注册自定义指令 //指令名称clickoutside Vue.directive('clickoutside',{ //钩子函数bind bind:function(el,binding,vnode){ /* * el:指令所绑定的元素,可以用来操作DOM * bingding:一个对象,拥有自己的属性 * vnode:Vue编译生成的虚拟节点,这里用不上 */ function documentHandler(e) { //e:鼠标点击的DOM元素 if(el.contains(e.target)){ //点击的区域e.target是否在所绑定园区el的内部 return false; } if(binding.expression){ //判断我们的指令是否绑定了函数 // <div class="main" v-clickoutside="handleClose">,实际我们指令绑定了handleClose函数 //执行我们绑定的指令,这里e可传可不传,传入后可以在绑定的方法中操作DOM元素 binding.value(e); } } //为el添加一个属性,绑定documentHandler方法,用于解绑时删除 el.__vueClickOutside__=documentHandler; //为document添加click事件 document.addEventListener('click',documentHandler); }, //解绑时,为document删除绑定的事件,因为无法用this,所以这里用el来承载 unbind:function (el, binding) { document.removeEventListener('click',el.__vueClickOutside__); delete el.__vueClickOutside__; } })
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="style.css" type="text/css"> </head> <body> <div id="app" v-cloak> <div class="main" v-clickoutside="handleClose"> <button @click="show =! show">点击显示下拉菜单</button> <div class="dropdown" v-show="show"> <p>下拉框的内容,点击外面区域可关闭</p> </div> </div> </div> <script src="vue.min.js"></script> <script src="clickoutside.js" type="application/javascript"></script> <!--包含new Vue()的组件需要放最后面--> <script src="index.js" type="application/javascript"></script> </body> </html>
style.css
[v-cloak]{ display: none; } .main{ width: 125px; } button{ display: block; width: 100%; color:#fff; background-color: #39f; border: 0px; padding: 6px; text-align: center; font-size: 12px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; cursor: pointer; outline: none; position: relative; } button:active{ top: 1px; left: 1px; } .dropdown{ width: 100%; height: 150px; margin: 5px 0; font-size: 12px; background-color: #fff; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 0 1px 6px rgba(0,0,0,.2); } .dropdown p{ display: inline-block; padding: 6px; }
扩展:使用esc键也可以关闭下拉框,应该如何做?
8.2.2 开发一个实时时间转换指令v-time
给定一个毫秒级别的时间戳,显示成指定的格式
index.js
var app=new Vue({ el:"#app", methods:{ }, data:{ timeNow:(new Date()).getTime(), timeBefore:1559373149411 } });
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>v-time</title> <link rel="stylesheet" href="style.css"> </head> <body> <div id="app" v-cloak> <div v-time="timeNow"></div> <div v-time="timeBefore"></div> </div> <script src="vue.min.js"></script> <script src="time.js"></script> <script src="index.js"></script> <script type="application/javascript"> </script> </body> </html>
time.js
var Time={ getUnix:function () { var date=new Date(); return date.getTime(); }, getTodayUnix:function () { //获取今天0时0分0秒的时间戳 var date=new Date(); date.setHours(0); date.setMinutes(0); date.setSeconds(0); date.setMilliseconds(0); return date.getTime(); }, getYearUnix:function () { //获取今年1月1日 0时0分0秒的时间戳 var date=new Date(); date.setMonth(0); date.setDate(1); date.setHours(0); date.setMinutes(0); date.setSeconds(0); date.setMilliseconds(0); return date.getTime(); }, getLastDate:function (time) { //获取标准年月日 var date=new Date(time); var month=date.getMonth()+1<10 ? '0'+ (date.getMonth()+1):date.getMonth()+1; var day=date.getDate()<10 ? '0'+date.getDate():date.getDate(); return date.getFullYear()+"-"+month+"-"+day; }, getFormatTime:function (timestamp) { //timestamp:服务器传的时间戳时间,比如文件创建的时间戳 var now=this.getUnix();//获取当前时间戳 var today=this.getTodayUnix();//获取今天0点时间戳 var year=this.getYearUnix();//获取今年1月1日0点时间戳 var timer=(now-timestamp) / 1000;//毫秒转换为秒 //Math.floor()向下取整 //Math.ceil()向上取整 var tip="" if(timer<0){ tip="刚刚" }else if(Math.floor(timer/60)<=0){ //1分钟内 tip="刚刚" }else if(timer<3600){ //大于1分钟,小于1个小时 tip=Math.floor(timer/60) + "分钟前" }else if(timer>=3600 && (timestamp-today >= 0)){ //大于1小时,但是在今天内 tip=Math.floor(timer/3600)+"小时前" }else if(timer/86400<=31){ //一个月内 tip=Math.ceil(timer/86400)+"天前" }else { tip=this.getLastDate(timestamp) } return tip } }; Vue.directive('time',{ bind:function (el, binding) { el.innerHTML=Time.getFormatTime(binding.value); //定时器,一分钟更新一次;这里给保存下来,在unbind中可对其删除 el.__timeout__=setInterval(function () { el.innerHTML=Time.getFormatTime(binding.value); },60000); }, unbind:function (el) { clearInterval(el.__timeout__); delete el.__timeout__; } })