浅谈 Vue 指令

谈到 Vue 指令,我们脑海里浮现的第一个疑问便是 指令 是什么:

指令是告诉计算机从事某一特殊运算的代码。如:数据传送指令、算术运算指令、位运算指令、程序流程控制指令、串操作指令、处理器控制指令。

那么 Vue 指令又是什么呢?是用来干什么的?作为一名攻城狮的我们又如何去使用它?

Vue 内置指令

1. 内置指令的使用

  • v-if:根据其后表达式的 bool 值进行判断是否渲染该元素
  • v-show:其后表达式的 bool 值为 false 时,对渲染的出标签添加display:none;的样式
  • v-else:紧跟着v-if或者v-show一起使用
  • v-for:v-for的用法 person in people ,前者是后者的元素,类似于数组的用法
  • v-bind:(:):用于响应地更新 html 特性
  • v-on:(@):用于监听指定元素的 DOM 事件
  • v-once:一次性插入文本,随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。
  • v-html:输出 {{ message }} 内包含 html 代码的数据

tips: 不能使用 v-html 来复合局部模板,因为 Vue 不是基于字符串的模板引擎,反之,对于用户界面(UI),组件更适合作为可重用和可组合的基本单位。另外,只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值,避免xss攻击。

2. 修饰符

一般用于指出 v-on 指令以特殊方式绑定

事件修饰符

<!-- prevent 一般用来阻止标签的点击默认行为,a 标签的点击跳转-->
<a v-on:submit.prevent="onSubmit">...</a>
<!--阻止事件冒泡-->
<div @click='doThis' style="width:100px;height: 100px; background: red;">
// 点击父元素
<a v-on:click.stop="doThis">点击子元素</a>
</div>

descripation:当点击父元素的时候,执行 doThis ,当点击子元素 a 的时候,这个点击动作不单单触发了 a 标签,同时也触发了div标签,这就是事件冒泡,所以假设上述例子中 a 标签为v-on:click='doThis',则 doThis 会被执行两次,父元素和子元素都执行了一次 click 事件,而 .stop 则是阻止事件冒泡,再次点击 a 标签,click 事件只会执行一次.

按键修饰符
Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

<input v-on:keyup.13="submit">

记住所有的 keyCode 比较困难,所以 Vue 为最常用的按键提供了别名:

<input v-on:keyup.enter="submit">
<input @keyup.enter="submit">

按键别名包括:

.enter .tab .delete (捕获 “删除” 和 “退格” 键) .esc .space .up .down .left .right .ctrl .shift .meta(windows 键,mac-command 键,)

Vue自定义指令

Vue 推崇数据驱动视图的理念(数据交互,状态管理),但并非所有情况都适合数据驱动( DOM 的操作)。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的 DOM 操作,并且是可复用的。

1. 定义Vue指令的方法

Vue.directive(id,definition)

description: 传入两个参数,指令ID和定义对象,定义对象提供了一些钩子函数。

2. 钩子函数

Vue.directive('my-directive', {
  bind: function(){
    // 指令第一次绑定到元素时调用,做绑定的准备工作
    // 比如添加事件监听器,或是其他只需要执行一次的复杂操作
  },
  inserted: function(){
     // 被绑定标签的父节点加入 DOM 时立即触发
  },
   update: function(){
    // 根据获得的新值执行对应的更新
    // 对于初始值也会调用一次
  },
  componentUpdated: function(){
    // 指令所在组件的 VNode 及其子 VNode 全部更新后调用,一般使用 update 即可
  },
  unbind: function(){
    // 做清理操作
    // 比如移除bind时绑定的事件监听器
  }
})

当只是用到 update 函数的时候,可以简化写法

Vue.directive('my-directive', function(){
  // update 内的代码块
})

目前,对 5个钩子函数的触发时机有了初步的认识。存疑的是 bind 和 inserted、update 和 componentUpdated 的区别了。

  • bind 和 inserted 的区别
<div id="app">
    <input v-focus>
</div>
<script>
    // 注册一个全局自定义指令v-focus
    Vue.directive('focus', {
    // 当绑定元素插入到DOM中
        inserted: function (el) {
        // 聚焦元素
            el.focus()
        }
        //聚焦不到元素
        bind: function(el){
            el.focus()
        }
    });
    var app = new Vue({
        el: '#app'
    });
</script>

description: 以上例子中,如果将代码写在 bind 钩子函数内,el.focus() 并未生效,这是因为在 bind 钩子函数被调用时,虽然能够通过 bind 的第一个参数 el 拿到对应的 DOM 元素,但是此刻该 DOM 元素还未被插入进 DOM 树中,因此在这个时候执行 el.focus() 是无效的。

当 DOM 元素被插入进 DOM 树中时,inserted 钩子就会被调用,因此在 inserted 中执行 el.focus() 是可以生效的。

  • update 和 componentUpdated 的区别
update: function (el, binding, vnode) {
    console.log('update')
    console.log(el.innerHTML)   // Hello
},
componentUpdated: function (el, binding, vnode) {
    console.log('componentUpdated')
    console.log(el.innerHTML)   // Hi
}

update 钩子函数触发时机是自定义指令所在组件的 VNode 更新时, componentUpdated 触发时机是指令所在组件的 VNode 及其子 VNode 全部更新后。此处使用 el.innerHTML 获取 data 值,从运行结果上看 update 和 componentUpdated 是 DOM 更新前和更新后的区别。

3. 参数所包含属性的意义

所有的钩子函数会被复制到实际的指令对象中,而这个指令对象将会是所有钩子函数的this上下文环境。指令对象上暴露了一些有用的公开属性。

  • el: 指令所绑定的元素,可以用来直接操作DOM 。

  • binding: 一个对象,包含以下属性:

  • name: 指令名,不包括 v- 前缀。

  • value: 指令的绑定值, 例如: v-directive="1 + 1",value 的值是 2

  • oldValue: 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。

  • expression: 绑定值的字符串形式。 例如 v-directive="1 + 1" , expression 的值是 "1 + 1"

  • arg: 传给指令的参数。例如 v-directive:foo, arg 的值是 "foo"

  • modifiers: 一个包含修饰符的对象。 例如: v-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true }

  • vnode: Vue 编译生成的虚拟节点。

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

tips: 这些属性是只读的,不要修改它们。你也可以给指令对象附加自定义的属性,但是注意不要覆盖已有的内部属性。

eg: 定义一个使用了 binding 参数的指令,以下的是都是生成的虚拟的节点,插入到 div:#example3 节点中:

<div id="example3" v-parameter:red="message"></div>
Vue.directive('parameter', {
    bind: function(el, binding, vnode){
        el.style.color = '#fff'
        el.style.backgroundColor = binding.arg
        el.innerHTML ='指令名name - '+ binding.name + '<br>' +'指令绑定值value - '+ binding.value + '<br>' +'指令绑定表达式expression - ' + binding.expression + '<br>'+'传入指令的参数argument - '+ binding.arg + '<br>'
    },
});
var demo = new Vue({
    el: '#example3',
    data: {
        message: 'hello,v-parameter'
    }
})

// 运行结果为(实际运行结果背景色应该为红色,字体颜色应该为白色)
// "指令名 name-parameter"
// "指令绑定值 value-hello,v-parameter!"
// "指令绑定表达式 express-message"
// "传入指令的参数 argument-red"

Vue自定义指令优先级顺序

  • 系统默认指令会先于自定义指令执行
  • 自定义指令在标签上的位置越靠前就越早执行
<!-- v-show 先于 v-block 执行 -->
<div v-block v-show="false"></div>

<!-- v-none 先于 v-block 执行 -->
<div v-none v-block></div>

定义这两个简单的指令

Vue.directive("block",{
    inserted:function (el) {
        el.style.display = "block";
    }
})
Vue.directive("none",{
    inserted:function (el) {
        el.style.display = "none";
    }
})

Vue指令的用途

1. 用来操作DOM

尽管Vue推崇数据驱动视图的理念,但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的 DOM 操作,并且是可复用的。

eg: 很多时候我们会遇到图片加载慢的问题,那么,在图片未完成加载前,可以用随机的背景色占位,图片加载完成后才直接渲染出来。˙这里,用自定义指令可以非常方便的实现这个功能。

tips: 本例的调试需要在控制台上如下操作:Network -> Offline Oline -> Slow 3G,在网络延迟的情况下更容易看出占位效果来。

Vue.directive('img',{
    //DOM
    inserted:function(el,binding){
        var color =Math.floor(Math.random()*1000000);
        el.style.backgroundColor = '#' + color;
        var img = new Image();
        img.src = binding.value;
        img.onload = function(){
            el.style.backgroundImage = 'url(' + binding.value + ')';
        }
    }
})
<div v-img="val.url" v-for="val in list"></div>
//此处图片路径为示意结果,为了能够更好的看出本段测试代码的效果,建议大家选择网上比较高清的图片
list:[
    {url:'1.jpg'},
    {url:'1.jpg'},
    {url:'1.jpg'}
]

2. 用于集成第三方插件

我们知道任何软件开发领域都可以分为四层:底层是原生的API,上层是通用框架,再上层是通用组件,最上层才是具体的业务代码。一个通用框架,必须搭配一套完整的通用组件,才能够很快的被广泛认可。
在前端开发领域,以前的通用框架是 jQuery,jQuery 以及基于 jQuery 构建的通用组件形成了一个庞大的生产系统。现在的通用框架是 Angular、React和Vue ,每个框架都需要基于自身构建新的组件库。自定义指令好就好在:原先的那些通用组件,无论是纯js的也好,基于 jQuery 的也好,都可以拿来主义直接吸收,而不需要改造或重构。

eg: 写文档通常会用到 highlight.js,我们可以直接将其封装为一个自定义指令,这样 highlight.js 就变成了 Vue 的一个新功能。

var hljs = require('highlight.js');
Vue.directive('highlight',function(el){
    hljs.hightlightBlock(el);
})
<pre>
    <code v-hightlight>&lt;alert-menu
        :menudata="menu"
        :e="eventObj"
        ref="menu"
        v-on:menuEvent="handle"&gt;
        &lt;/alert-menu&gt;
    </code>
</pre>

运行结果:

输出<alert-menu>标签里的所有内容,而且按照 html 的高亮显示规则显示。

tips: 所以但凡遇到第三方插件如何与 Vue.js 集成的问题,都可以尝试用自定义指令实现。

Vue指令和Vue组件之间的关系

很多时候,对于初学者来说,看完指令的使用会发现组件的使用和指令的自定义有几分相似之处。其实,并非如此,组件和指令完全不是一个层级上的概念。打个比方:组件是一个房子,它可以嵌套使用,房子里边又有窗户,门,桌子,床,柜子等这些子组件。而指令是附着在组件上的某种行为或者功能,门和窗户可以打开关闭,桌子可以折叠,柜子可以打开关上等等。以下是对于组件和指令的定义,希望能够让大家更清晰的理解:

  • 组件:一般是指一个独立实体,组件之间的关系通常都是树状。
  • Vue指令:用以改写某个组件的默认行为,或者增强使其获得额外功能,一般来说可以在同一个组件上叠加若干个指令,使其获得多种功能。比如 v-if,它可以安装或者卸载组件。

最佳实践

根据需求的不同,我们要选择恰当的时机去初始化指令、更新指令调用参数以及释放指令存在时的内存占用等。一个健壮的库通常会包含:初始化实例、参数更新和释放实例资源占用等操作。

Vue.directive('hello', {
    bind: function (el, binding) {
        // 在 bind 钩子中初始化库实例
        // 如果需要使用父节点,也可以在 inserted 钩子中执行
        el.__library__ = new Library(el, binding.value)
    },
    update: function (el, binding) {
        // 模版更新意味着指令的参数可能被改变,这里可以对库实例的参数作更新
        // 酌情使用 update 或 componentUpdated 钩子
        el.__library__.setOptions(Object.assign(binding.oldValue, binding.value))
    },
    unbind: function (el) {
        // 释放实例
        el.__library__.destory()
    }
})

总结回顾

通过以上 Vue 指令的学习,以及诸多 demo 的实现,我们便可清晰的认识 Vue 指令了,并且能逐一解答我们最开始内心的疑虑了:

  • Vue 指令就是以 “v-” 开头,作用于 DOM ,为 DOM 添加特殊行为的一种指令。
  • 自定义指令则是对 Vue 指令的一种有效的补充和扩展,不仅可用于定义任何的 DOM 操作,并且是可复用的。
  • Vue 指令使用大部分是和组件结合着使用,从而增强组件的功能。甚至部分 Vue 指令能够直接安装卸载组件,例如v-if
  • Vue 自定义指令最重要的是我们对 bind inserted update componentUpdated unbind五个钩子函数的理解,以及对钩子函数中el binding(属性值) vnode oldVnode四个参数的使用,只要这些内容能够熟练使用,我们便可以自己去定义适合各种开发场景的 Vue 指令了。
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值