指令的注册

自定义指令的注册分为全局注册和局部注册。
语法:Vue.directive(id,definition)。id 是指令的唯一标识,definition 定义对象则是指令的相关属性及钩子函数

全局自定义指令

  1. Vue.directive('focus', { // 注册一个全局自定义指令 v-focus
  2. //定义对象
  3. })

注册局部指令

  1. var vm = new Vue({
  2. el: '#app',
  3. directives:{
  4. focus:{
  5. //定义对象
  6. }
  7. }
  8. })

指令的定义对象

definition定义对象,对指令赋予具体的功能。可以提供如下几个钩子函数 (均为可选):
bind:只调用一次,指令第一次绑定到元素时调用,在这里可以进行一次性的初始 化设置。
inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 ocument 中)。
update:被绑定元素所在的模板跟新时调用,而不论绑定值是否变化。通过比较更 新前后绑定的值,可以忽略不必要的模板更新 。
componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
unbind:只调用一次,指令与元素解绑时调用。
根据需求在不同的钩子函数内完成逻辑代码。

(全局/局部) 自定义 v-focus 指令

  1. <!DOCTYPE html>
  2. <html xmlns:v-on="http://www.w3.org/1999/xhtml" xmlns:v-bind="http://www.w3.org/1999/xhtml">
  3. <head>
  4. <title></title>
  5. <meta charset="utf-8" />
  6. <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="app">
  10. <p>页面载入时,input 元素自动获取焦点:</p>
  11. <input v-focus>
  12. </div>
  13. <script>
  14. // 注册一个全局自定义指令 v-focus
  15. /* Vue.directive('focus', {
  16. // 当绑定元素插入到 DOM 中。
  17. inserted: function (el) {
  18. el.focus(); // 聚焦元素
  19. }
  20. })*/
  21. // 创建根实例
  22. var vm = new Vue({
  23. el: '#app',
  24. directives: {
  25. // 注册一个局部的自定义指令 v-focus
  26. focus: {
  27. // 指令的定义
  28. inserted: function(el) {
  29. // 聚焦元素
  30. el.focus()
  31. }
  32. }
  33. }
  34. })
  35. </script>
  36. </body>
  37. </html>

指令实例属性

在指令的钩子函数内,可以通过 this 来调用指令实例。下面详细说明指令的实例属性。
el:指令所绑定的元素,可以用来直接操作 DOM。
binding:一个对象,包含以下 property
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-my-directive=”1 + 1” 中,绑定值为 2。
oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中 可用。无论值是否改变都可用。
expression:字符串形式的指令表达式。例如 v-my-directive=”1 + 1” 中,表达 式为 “1 + 1”。
arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符 对象为 { foo: true, bar: true }。
vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

  1. <!DOCTYPE html>
  2. <html xmlns:v-on="http://www.w3.org/1999/xhtml" xmlns:v-bind="http://www.w3.org/1999/xhtml"
  3. xmlns:v-demo="http://www.w3.org/1999/xhtml">
  4. <head>
  5. <title></title>
  6. <meta charset="utf-8"/>
  7. <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  8. </head>
  9. <body>
  10. <div id="app" v-demo:msg.a.b="message"></div>
  11. </div>
  12. <script>
  13. Vue.directive('demo', {
  14. bind: function (el, binding, vnode) {
  15. var s = JSON.stringify
  16. el.innerHTML =
  17. 'name: ' + s(binding.name) + '<br>' +
  18. 'value: ' + s(binding.value) + '<br>' +
  19. 'expression: ' + s(binding.expression) + '<br>' +
  20. 'argument: ' + s(binding.arg) + '<br>' +
  21. 'modifiers: ' + s(binding.modifiers) + '<br>' +
  22. 'vnode keys: ' + Object.keys(vnode).join(', ')
  23. }
  24. })
  25. new Vue({
  26. el: '#app',
  27. data: {
  28. message: 'hello!'
  29. }
  30. })
  31. </script>
  32. </body>
  33. </html>

下拉菜单

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title></title>
  5. <meta charset="utf-8"/>
  6. <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="app">
  10. <!--自定义指令v-clickoutside绑定handleHide函数-->
  11. <div class="main" v-clickoutside="handleHide">
  12. <button @click="show = !show">点击显示下拉菜单</button>
  13. <div class="dropdown" v-show="show">
  14. <div class="item"><a href="#">选项 1</a></div>
  15. <div class="item"><a href="#">选项 2</a></div>
  16. <div class="item"><a href="#">选项 3</a></div>
  17. </div>
  18. </div>
  19. </div>
  20. <script>
  21. /*自定义指令v-clickoutside*/
  22. Vue.directive('clickoutside', {
  23. /*在document上绑定click事件,所以在bing钩子函数内声明了一个函数
  24. documentHandler,并将它作为句柄绑定在document的click事件上。
  25. documentHandler函数做了两个判断。
  26. * */
  27. bind(el, binding) {
  28. function documentHandler(e) {
  29. /*第一个是判断点击的区域是否是指令所在的元素内部,如果是,
  30. 就跳转函数,不往下继续执行。
  31. * contains方法是用来判断元素A是否包含了元素B,包含返回true。
  32. * */
  33. if (el.contains(e.target)) {
  34. return false
  35. }
  36. /*第二个判断的是当前的指令v-clickoutside有没有表达式,在该自定义
  37. 指令中,表达式应该是一个函数,在过滤了内部元素后,点击外面任何区
  38. 域应该执行用户表达式中的函数,所以binding.value()
  39. * 就是用来执行当前上下文methods中指定的函数的。
  40. * */
  41. if (binding.expression) {
  42. binding.value(e)
  43. }
  44. }
  45. /*与Vue 1.0不同的是,在自定义指令中,不能再使用this.xxx的形式再上下
  46. 文中声明一个变量,所以用了el.__vueMenuHandler__引用了documentHandler,
  47. 这样就可以再unbind钩子里移除对document的click
  48. 事件监听。如果不移除,当组建或元素销毁时,它任然存在于内存中。
  49. * */
  50. el.__vueMenuHandler__ = documentHandler;
  51. document.addEventListener('click', el.__vueMenuHandler__)
  52. },
  53. unbind(el) {
  54. document.removeEventListener('click', el.__vueMenuHandler__)
  55. delete el.__vueMenuHandler__
  56. }
  57. })
  58. new Vue({
  59. el: '#app',
  60. data: {
  61. show: false
  62. },
  63. methods: {
  64. handleHide() {
  65. this.show = false
  66. }
  67. }
  68. })
  69. </script>
  70. </body>
  71. </html>

相对时间转换

在很多社区网站,比如朋友圈、微博等,发布的动态有一个相对本机时间转换后的相对时间。
我们来实现这样一个Vue自定义指令v-time,将表达式传入的时间戳实时转换为相对时间。为了便于演示效果,我们初始化时定义了两个时间

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>时间转换指令</title>
  5. <meta charset="utf-8"/>
  6. <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="app">
  10. <div v-time="timeNow"></div>
  11. <div v-time="timeBefore"></div>
  12. </div>
  13. <script src="./time.js"></script>
  14. <script src="./index.js"></script>
  15. <script>
  16. var vm = new Vue({
  17. el: '#app',
  18. data: {
  19. timeNow: (new Date()).getTime(),
  20. timeBefore: 1580503571085
  21. }
  22. })
  23. </script>
  24. </body>
  25. </html>

timeNow是当前的时间,
timeBefore是一个写死的时间:2020-02-01。

在写指令v-time之前,需要先写一系列与时间相关的函数 ,我们声明一个对象Time,把它们都封装到里面

  1. var Time = {
  2. //获取当前时间戳
  3. getUnix: function() {
  4. var date = new Date();
  5. return date.getTime();
  6. },
  7. //获取今天0点0分0秒的时间戳
  8. getTodayUnix: function() {
  9. var date = new Date();
  10. date.setHours(0);
  11. date.setMinutes(0);
  12. date.setSeconds(0);
  13. date.setMilliseconds(0);
  14. return date.getTime();
  15. },
  16. //获取今年1月1日0点0秒的时间戳
  17. getYearUnix: function() {
  18. var date = new Date();
  19. date.setMonth(0);
  20. date.setDate(1);
  21. date.setHours(0);
  22. date.setMinutes(0);
  23. date.setSeconds(0);
  24. date.setMilliseconds(0);
  25. return date.getTime();
  26. },
  27. //获取标准年月日
  28. getLastDate: function(time) {
  29. var date = new Date(time);
  30. var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1;
  31. var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
  32. return date.getFullYear() + '-' + month + '-' + day;
  33. },
  34. //转换时间
  35. getFormateTime: function(timestamp) {
  36. var now = this.getUnix();
  37. var today = this.getTodayUnix();
  38. var year = this.getYearUnix();
  39. var timer = (now - timestamp) / 1000;
  40. var tip = '';
  41. if (timer <= 0) {
  42. tip = '刚刚';
  43. } else if (Math.floor(timer / 60) <= 0) {
  44. tip = '刚刚';
  45. } else if (timer < 3600) {
  46. tip = Math.floor(timer / 60) + '分钟前';
  47. } else if (timer >= 3600 && (timestamp - today >= 0)) {
  48. tip = Math.floor(timer / 3600) + '小时前';
  49. } else if (timer / 86400 <= 31) {
  50. tip = Math.ceil(timer / 86400) + '天前';
  51. } else {
  52. tip = this.getLastDate(timestamp);
  53. }
  54. return tip;
  55. }
  56. }

Time.getFormatTime()方法就是自定义指令v-time所需要的,参数为毫秒级时间戳,返回已经整理好的时间格式的字符串。
最后在time.js里补全剩余的代码如下

  1. Vue.directive('time', {
  2. bind: function(el,binding) {
  3. el.innerHTML = Time.getFormateTime(binding.value);
  4. el.__timeout__ = setInterval(() => {
  5. el.innerHTML = Time.getFormateTime(binding.value);
  6. },60000);
  7. },
  8. unbind: function() {
  9. clearInterval(el.__timeout__);
  10. delete el.__timeout__;
  11. }
  12. })

在bind钩子里,将指令v-time表达式的值binding.value作为参数传入Time.getFormatTime()方法中得到格式化时间,在通过el.innerHTML写入指令所在元素。定时器el.timeout每分钟触发一次,更新时间,并且在unbind钩子里清除掉。