jd-smart-fe / shared

共享文档
MIT License
25 stars 4 forks source link

浅谈 Vue 指令 #7

Closed zhangyongqiang010 closed 5 years ago

zhangyongqiang010 commented 6 years ago

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

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

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

Vue 内置指令

1. 内置指令的使用

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 的区别了。

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

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

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

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.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 指令了,并且能逐一解答我们最开始内心的疑虑了: