Vue.js破冰系列-3自定义指令

1 指令的格式及注册

上一章我们讲了vue的内部指令,下例是将divClicked方法添加click事件监听中,同时停止click冒泡事件:

<div v-on:click.stop="divClicked">hello</div>

由上可以得出指令的一般格式,其中指令名称为必填,其他为可选项,格式如下:

指令名[:参数][.修饰符][=表达式]

vue提供的内部指令有时不能满足我们的需求,不过vue允许我们使用directive自定义指令,自定义指令分为全局注册和组件局部注册

//全局注册
Vue.directive('指令名',{
  //指令选项
})

//局部注册
var vm = new Vue({
  el:'#app',
  directives:{
    指令名:{
      //指令选项
    }
  }
})

全局指令使用directive命令,局部注册使用directives选项。vue会在指令名称名上加v-前缀。如果我们注册了一个名为drag的拖拽指令,使用时应为v-drag。

Vue.directive('drag',{
  //指令选项
});

//<div v-drag>可拖动块</div>

2 指令选项

指令选项定义指令的行为,这些选项是可选的,每个选项都对应一个钩子函数,一共有5种:

  • bind在指令绑定到元素时调用,它只执行一次,可在这个选项中设置被绑定元素的监听事件等。
  • inserted当被绑定元素插入到父节点时调用,注意:他的触发时机是插入到父节点,而不管这个父节点是否被渲染到界面中。
  • update被绑定元素所在模板更新时调用,注意绑定元素的模板的更新,不一定是被绑定元素引起的,所以可以通过比较更新前后的值来忽略不必要的元素更新。
  • componentUpdated被绑定元素所在模板完成一次更新周期时调用。
  • unbind指令与元素解绑时调用,他与bind选项对应,也是只调用一次,可在这个选项中取消被绑定元素的监听。

3 钩子函数参数

每个选项的钩子函数都会被传入以下参数:

  • el:表示指令绑定的元素,通过它可以直接操作DOM,比如我们的v-html指令就是通过el参数去设置它的innerHTML值。

  • binding:将指令的相关信息封装到这个对象中,比如,指令的名称name,参数arg,表达式expression,修饰符modifiers等信息,在说这个对象的key之前,我们再看一下指令的格式:

    指令名name[:参数arg][.修饰符modifiers][=表达式expression]
    

    通过这个对象可以得到指令的所有信息,具体如下:

    • name 指令名称,不含v-前缀
    • arg 指令参数
    • modifiers修饰符对象,我们知道一个指令可以添加多个修饰符,这里使用对象语法来表示修饰符,如果name指令的格式是这样v-name:arg.foo.bar,那么,modifiers对象为{foo:true,bar:true},注意这是一个对象而不是数组。
    • expression 表示指令的表达式,它会将表达式以字符串的形式呈现,不会去执行。
    • value 是expression计算的结果。
    • oldValue 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。
  • vnode Vue 编译生成的虚拟节点

  • oldVnode上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用

目前,我们能用到的是el和binding两个参数,vnode和oldVnode将在后续的章节中涉及。有了上面知识,我们注册一个指令的格式大致是这样的:

Vue.directive('drag',{
    bind:function(el,binding){
        //指令绑定到el上,只调用一次
    },
  insert:function(el,binding){
    //el插入到父节点
  },
  update:function(el,binding){
    //el所在模板更新
    let {value,oldValue} = binding;
    //可能不是el引起的(el的绑定值未变化)
    if(value === oldValue){
      //不是绑定值在更新前后无变化,所以不用做更新操作
      return;
    }
    //更新操作
  },
  componentUpdated:function(el,binding){
    //el所在模板完成一次更新周期时调用
  },
  unbind:function(el,binding){
    //指令与el解绑,只调用一次
  },
});

3 自定义指令实例

3.1 拖拽指令

一个元素要拖拽,我需要设置它的position属性为absolute,然后根据鼠标的移动位置动态的设置该元素的left和top值。本例的自定义的拖拽指令不需要指令的参数,修饰符和表达式等信息。

<style>
  .box {
    width:100px;
    height:100px;
    background:red;
  }
</style>
<body>
  <div id="app">
    <div class="box" v-drag></div>
  </div>
  <script>
    Vue.directive('drag', {
      bind: function (el) {
        //拖拽需要被绑定元素使用绝对定位
        el.style.position = 'absolute';
        el.onmousedown = function (e) {
          //鼠标在元素内部的偏移量
          let offsetX = e.offsetX;
          let offsetY = e.offsetY;
          document.onmousemove = function (e) {
            el.style.left = e.clientX - offsetX + 'px';
            el.style.top = e.clientY - offsetY + 'px';
          };
          el.onmouseup = function () {
            document.onmousemove = null;
            el.onmouseup = null;
          };
        };
      }
    });
    var vm = new Vue({
      el: "#app",
    });
  </script>
</body>

注意:这个拖拽指令设置了元素的position为绝对定位,由于绝对定位的基准点(left和top为0的点)规则为:

离自己最近的且有定位属性(relatvie,absolute,fixed,但是不含static)的祖先元素,如果没有定位属性的祖先元素,则以body为基准

而我们设置的元素的left和top的值是以document(可以理解为body元素)的位置为基础,所以,当设置了v-drag指令的元素,存在有定位属性的祖先元素时,这个指令存在bug,因为元素的基准点不在是document的原点位置,而是祖先元素的原点位置。

3.2 格式化货币指令

有些时候需要给数字按3位加逗号的需求,我们可以使用自定义指令和正则表达式来完成这需求,如果你对下面用到的正则表达式不熟悉,可以参考我的正则表达式这篇文章。

<body>
  <div id="app">
    <input type="text" v-model="total">
    <br>
    <span v-formatter="total"></span>
  </div>
  
  <script>
    var Utils = {
      formmatter(el,value){
        value = value + "";
        let hasDot = /\./g;
        let patt = /\B(?=(\d{3})+\.)/g
        if (!hasDot.test(value)) {
          //没有小数点的正则表达式
          patt = /\B(?=(\d{3})+$)/g
        }
        let text = value.replace(patt, ",");
        el.innerText = text;
      }
    }
    
    Vue.directive('formatter', {
      bind:function(el,binding){
        let { value } = binding;
        Utils.formmatter(el,value);
      },
      update: function (el, binding) {
        let { value,oldValue } = binding;
        if (value === oldValue) {
          return;
        }
        Utils.formmatter(el,value)
      },
    });
    
    var vm = new Vue({
      el: "#app",
      data: {
        total: 1234567890,
      }
    });
  </script>
</body>

上面用了bind和update两个钩子函数,一个是在初始化时格式化初始值,另一个是当total的值发生变化时,实时格式化。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容