Vue之自定义指令

先了解一下,在 vue 中,有很多内置的指令.

比如:

  • v-for 用于遍历
  • v-if & v-show 用于隐藏和显示元素(区别在于后者是修改 display:block|none,前者是不创建把元素从dom中删除或者创建.
  • v-bind: 属性绑定,把数据绑定在HTML元素的属性上.
  • v-html & v-text 把数据绑定在HTML元素的属性上,作用同 innerHTML & innerText
  • v-on: 绑定HTML元素事件
  • v-if & v-else-if & v-else 条件渲染
  • v-model 绑定表单元素,实现双向绑定.

等等.....

所以,关于指令,我们可以总结下面几点:

  • 指令是写在 HTML 属性地方的.<input v-model='name' type='text' />
  • 指令都是以 v- 开头的.
  • 指令表达式的右边一般也可以跟值 v-if = false

2.0 Vue自定义指令场景小DEMO(非常尬)

场景:

我们需要一个指令,写在某个HTML表单元素上,然后让它在被加载到DOM中时,自动获取焦点.

// 和自定义过滤器一样,我们这里定义的是全局指令
Vue.directive('focus',{
    inserted(el) {
      el.focus()
    }
})

<div id='app'>
    <input type="text">
    <input type="text" v-focus placeholder="我有v-focus,所以,我获取了焦点">
  </div>
  

这里放了两个 input ,但是后面的 input 才使用了我们的自定义 v-focus 指令,所以看到了是后面那个文本框获取了焦点,而不是前面一个.

15446047297741.jpg

先总结几个点:

  • 使用 Vue.directive() 来新建一个全局指令,(指令使用在HTML元素属性上的)
  • Vue.directive('focus') 第一个参数focus是指令名,指令名在声明的时候,不需要加 v-
  • 在使用指令的HTML元素上,<input type="text" v-focus placeholder="我有v-focus,所以,我获取了焦点"/> 我们需要加上 v-.
  • Vue.directive('focus',{}) 第二个参数是一个对象,对象内部有个 inserted() 的函数,函数有 el 这个参数.
    • el 这个参数表示了绑定这个指令的 DOM元素,在这里就是后面那个有 placeholderinput
    • el 就等价于 document.getElementById('el.id')....
    • 可以利用 $(el) 无缝连接 jQuery

指令的生命周期

其实上面个例子很尬!
HTML5 本身就提供了一个 autofocus 的属性,我们直接写这个就OK了.

用指令我们需要:

  • 新增一个指令
  • 定义指令的第二个参数里的 inserted 函数
  • 在需要获取焦点的元素上,使用这个指令.

非常的费时费力....

在说明,为什么我要这么麻烦的使用指令之前,可以先了解一下指令的一些基本知识.

上述例子中,我们写了一个叫 inserted(){} 的函数,
有了 inserted 就说明了,指令在绑定到 HTML 元素上时,肯定也是有一堆钩子函数的,说白了也就是生命周期.

当一个指令绑定到一个元素上时,其实指令的内部会有五个生命周期事件函数.

先上官方说明:

  • bind(){} 当指令绑定到 HTML 元素上时触发.只调用一次.
  • inserted() 当绑定了指令的这个HTML元素插入到父元素上时触发(在这里父元素是 div#app).但不保证,父元素已经插入了 DOM 文档.
  • updated() 所在组件的VNode更新时调用.
  • componentUpdate 指令所在的组件的VNode以及其子VNode 全部更新后调用.
  • unbind: 指令和元素解绑的时候调用,只调用一次

Vue 指令的声明周期函数

Vue.directive('gqs',{
    bind() {
      // 当指令绑定到 HTML 元素上时触发.**只调用一次**
      console.log('bind triggerd')
    },
    inserted() {
      // 当绑定了指令的这个HTML元素插入到父元素上时触发(在这里父元素是 `div#app`)**.但不保证,父元素已经插入了 DOM 文档.**
      console.log('inserted triggerd')
    },
    updated() {
      // 所在组件的`VNode`更新时调用.
      console.log('updated triggerd')
    },
    componentUpdated() {
      // 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
      console.log('componentUpdated triggerd')
      
    },
    unbind() {
      // 只调用一次,指令与元素解绑时调用.
      console.log('unbind triggerd')
    }
  })

HTML

<div id='app' v-gqs></div>

结果:

bind triggerd
inserted triggerd

发现默认情况下只有 bindinserted 声明周期函数触发了.

那么剩下的三个什么时候触发呢?

 <div id='app' >
    <p v-gqs v-if="show">v-if是删除或者新建dom元素,它会触发unbind指令声明周期吗?</p>
    <button @click="show=!show">toggle</button>
  </div>

一开始猜测 unbind 应该是删除元素的时候触发,也是弄了个 v-if.

然后点击按钮,发现果然如此.
当指令绑定的元素被销毁时,会触发指令的 unbind 事件.
(新建并显示,仍然是触发 bind & inserted)

unbind触发.gif
 <p v-gqs v-show="show2">v-show设置元素的display:block|none,会触发componentUpdated事件</p>
    <button @click="show2=!show2">toggle-v-show</button>
componentUpdated.gif

unbind()componentUpdated()

  • 一个把元素从DOM删除触发unbind().---> 仅仅是删除.
  • 一个显示设置元素的隐藏和显示的时候触发 componentUpdated() ---> block | none 都触发.

但是 updated() 声明周期什么时候触发呢?

官方解释是 :

所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

反正有点看不懂..

所在组件的VNode??? 指令不是写在HTML元素上的吗?这里的组件指的是绑定了指令的HTML元素吗???

尝试用一个 list 列表来试验...

fruits:[
        '🍎',
        '🍌',
        '🍊',
        '🍇',
        '🍑'
      ]
<ul>
      <li v-gqs v-for="(f,i) in fruits" :key="i">{{f}} <a href="#" @click.prevent='delF(i)'>X</a></li>
    </ul>
updated测试1.gif
  • 仍然没有触发 updated().
  • 但是触发了一次 unbind(),原因是:我们删除了一个元素.当元素从DOM中删除时就会触发指令的 unbind()函数
  • 触发了4componentUpdated(),,之前认为的是 display:block|none,会触发,但这里也触发了4次,是不是由于剩下的4个item可能位置发生了改变也会触发这个???

测试绑定了位置改变的元素也会触发 componentUpdated()

<div id="card" v-gqs :class={move:isMove}></div>
    <button @click="isMove = true">move</button>
componentUpdated2.gif

果然,不光是隐藏显示,就连位置发生了改变,也会触发 componentUpdated()..

那既然是这样,只要元素的当前状态变了,就会触发 componentUpdated() 咯??

测试元素的内容发生了改变,将会触发componentUpdated()

componentUpdated3.gif

既然元素只要能再肉眼范围内发生改变,就能触发 componentUpdated(),
那可以使用这个特性来监控 input 元素的输入咯?(有没有卵用先放一边)

<input type="text" id='text' v-model="text" v-gqs>

componentUpdated(el) {
      if (el.id === 'text') {
        console.log(el.value)
      }
}
componentUpdated4.gif

最后,没有测出来 updated()的触发时机,倒是把 componentUpdated()测试出来了. 😓.

componentUpdated() 触发就在于,当前绑定了这个指令的元素,有任意改变.

注意:这里的改变必须是指和vue数据建立联系.

  • 显示/隐藏 (v-if & v-show)
  • 样式改变 (:class={active:isActive})
  • 内容改变 (v-model="text")

只要和vue数据绑定产生的不管是样式还是内容发生改变,那么就一定会触发 componentUpdated()

前提是:一定要和 vue 的数据建立联系!!!
前提是:一定要和 vue 的数据建立联系!!!
前提是:一定要和 vue 的数据建立联系!!!


所以 ,到底 update() 这个指令的钩子函数什么时候触发呢?

  • 元素删除,新建不触发
  • 各种形态转变也不触发....

所以,我就打算先把这个 updated() 钩子函数先放一放.

贴一个官方说明:

所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。


为什么我如此执着的想测试出updated()事件触发机制呢?

官方说明:

指令的简写形式:

在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写:

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

这里我怎么区分是触发的 bind() 还是 unpdate() 呢 ????

等等 是 update() 不是 updated()!!!!!

我勒个去,钩子函数名字写错了!!!!!!!
我勒个去,钩子函数名字写错了!!!!!!!
我勒个去,钩子函数名字写错了!!!!!!!

难怪测试不出来.

既然,我之前的测试结果是:

  • bind() --> 绑定触发,触发一次.
  • inserted() --> 元素插入父元素时触发.
  • componentUpdated() --> 元素状态发生改变时触发(包裹内容,样式)
  • unbind() --> 元素从DOM删除时触发.(仅仅是删除)

那按道理,既然是名字写错了update --> updated...
那么 update 就是在 componentUpdated 之前触发的咯....


修改钩子函数名字再测试 update() 钩子函数.

既然 update() & componentUpdated() 分成了两个钩子函数,执行顺序一前一后.

<input type="text" id='text' v-model="text" v-gqs>

// update !!! 不是  updated
 update(el) {
      // 所在组件的`VNode`更新时调用.
     
        console.log(`update triggerd :${el.value}`)
     
    },
    componentUpdated(el) {
       // 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
     
        console.log(`componentUpdated triggerd:${el.value}`)
     
    },    
update->componentUpdated.gif

update()componentUpdated() 前后执行顺序是对的.


简要的画一张图,来说明指令的这几个钩子函数

vue.指令声明钩子函数v2.0.png

利用指令,可以做一些小事情.

既然,指令并不是一次性的活.
当绑定指令的元素的状态发生改变时(这里主要是指元素绑定的vue数据发生改变时),仍然会触发指令中的 update 函数.

那么我们可以利用指令的简写形式,来做一些有意思的事情.

核心思想就是:

当一个HTML元素设置了指令,那么在这个元素的状态发生改变时(由vue数据变化带来的带来的监控),我们可以利用update()钩子函数监控到这个元素的变化,然后根据需要做一些其他的事情.

使用官方指定的指令简写模式:

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

inserted or update .

当元素的状态发生改变时,就会触发 update

在写demo之前,还需要了解一下指令钩子函数的几个参数.

  • el: 绑定指令的那个dom元素.(doucument.getElementById('el.id')
  • binding: 一个对象
    • name : v-gqs ==> gqs 不包括前面的v-
    • valule : 指令后面的值 v-gqs='abc' value=abc
    • oldValue 前一个值,只能在 update & componentUpdate 中使用.
    • expression : v-gqs='1+1',如果是 value = 2 ,如果是 expression = 1 + 1
    • arg: 指令传递的参数 ,比如 v-gqs:hello arg = hello
    • modifiers : 比如 v-gqs.a.b modifiers = {a:true,b:true}
  • vnode:Vue编译生成的虚拟节点.
  • oldVnode:上一个虚拟节点,仅在 update & componentUpdated 中可用.
Vue.directive('change-bgc', (el, binding,vnode,oldVnode) => {
    el.style.backgroundColor = 'red'
    console.log(`binding.name:${binding.name}`)
    console.log(`binding.value:${binding.value}`)
    console.log(`binding.expression:${binding.expression}`)
    console.log(`binding.arg:${binding.arg}`)
    console.log(`binding.modifiers:${JSON.stringify(binding.modifiers)}`)
    console.log(`binging.oldValue:${binding.oldValue}`)
    console.log(`vnode:${Object.keys(vnode).join(',')}`)
    console.log(`oldVnode:${JSON.stringify(oldVnode)}`)
  })

binding.name:change-bgc
binding.value:2
binding.expression:1+1
binding.arg:foo
binding.modifiers:{"left":true,"bottom":true}
binging.oldValue:undefined
vnode:tag,data,children,text,elm,ns,context,fnContext,fnOptions,fnScopeId,key,componentOptions,componentInstance,parent,raw,isStatic,isRootInsert,isComment,isCloned,isOnce,asyncFactory,asyncMeta,isAsyncPlaceholder
oldVnode:{"tag":"","data":{},"children":[],"raw":false,"isStatic":false,"isRootInsert":true,"isComment":false,"isCloned":false,"isOnce":false,"isAsyncPlaceholder":false}

DEMO

一个卡片应用,用户在页面上如果看到比较喜欢的卡片,可以钉在屏幕上.

先看看效果:

pinend2.gif

HTML


 <div id='app'>
    <div v-pin="card1.pinded" class="card">
      <a href="#" @click.prevent='card1.pinded = !card1.pinded'>钉住/取消</a>
      <img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1544621884322&di=ae2b10253c52ec699359f8c27a8097aa&imgtype=0&src=http%3A%2F%2Ff6.topitme.com%2F6%2F36%2Ff4%2F1118255893119f4366l.jpg"
        alt="">
    </div>
    <div v-pin="card2.pinded" class="card">
      <a href="#" @click.prevent='card2.pinded = !card2.pinded'>钉住/取消</a>
      <img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1544621884322&di=ae2b10253c52ec699359f8c27a8097aa&imgtype=0&src=http%3A%2F%2Ff6.topitme.com%2F6%2F36%2Ff4%2F1118255893119f4366l.jpg"
        alt="">
    </div>
</div>

.......

Vue.js

var app = new Vue({
    el: '#app',
    data: {
      card1: {
        pinded: false
      },
      card2: {
        pinded: false
      }
    },
  })

指令代码

// 如果你只需要执行绑定的 bind 和 update 两个事件,vue指令定义也配置了简写方式.
  let toppx = 10
  Vue.directive('pin', (el, binding) => {
    if (binding.value) {
      el.style.position = "fixed"
      el.style.left = '300px'
      let top = toppx
      el.style.top = top + 'px'
      toppx += 200
    } else {
      el.style.position = 'static'
      toppx === 10 ? toppx : toppx-=200
    }
  })

核心代码解释一下:

 <div v-pin="card1.pinded" class="card">
      <a href="#" @click.prevent='card1.pinded = !card1.pinded'>钉住/取消</a>
      </div>
  • 我们定住的按钮是包含在使用了 v-pin 指令的一个DIV内部的A标签.
  • A标签本身是没有绑定任何指令的.
  • 但是在A标签的click事件里,我们修改了 card1.pinded的值.
  • DIV里利用 v-pin 指令绑定了 card1.pinded
  • A 标签点击的时候,修改了 card1.pinded 的值,这个值是vue.$data的值,同时也修改了指令指向的那个值 binding.value.
  • 所以,最终就触发了 DIVv-pin 指令的 update 方法.

组件指令

和过滤器一样,指令也分 全局指令 & 组件指令

组件指令语法:


var app = new Vue({
    directives: {
      innerDirective:{
        bind() {

        },
        inserted() {

        },
        update() {

        },
        componentUpdated() {

        },
        unbind() {

        }
      }
    })

简写模式:

// 主要是 bind & update 钩子函数
 directives: {
      simpleDirective:(el,binding) =>{

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

推荐阅读更多精彩内容