Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

浅谈 Vue 指令 #7

Closed
zhangyongqiang010 opened this issue Jan 19, 2018 · 0 comments
Closed

浅谈 Vue 指令 #7

zhangyongqiang010 opened this issue Jan 19, 2018 · 0 comments

Comments

@zhangyongqiang010
Copy link
Contributor

zhangyongqiang010 commented Jan 19, 2018

谈到 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 指令了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant