指令

概念及语法

指令 (Directives) 是带有 v- 前缀的特殊属性。指令属性的值预期是单个 JavaScript 表达式(v-for 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。

例如 v-if 指令:

  1. <p v-if="seen">现在你看到我了</p>

这里,v-if 指令将根据表达式 seen 的值的真假来插入/移除 <p> 元素。

指令参数

一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML 属性:

  1. <a v-bind:href="url">...</a>

在这里 href 是参数,告知 v-bind 指令将该元素的 href 属性与表达式 url 的值绑定。

另一个例子是 v-on 指令,它用于监听 DOM 事件:

  1. <a v-on:click="doSomething">...</a>

在这里参数是监听的事件名。我们也会更详细地讨论事件处理。

指令修饰符

一些指令还可以添加指令修饰符,修饰符 (Modifiers) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()

  1. <form v-on:submit.prevent="onSubmit">...</form>

在接下来对 v-onv-for 等功能的探索中,你会看到修饰符的其它例子。

指令缩写

v- 前缀作为一种视觉提示,用来识别模板中 Vue 特定的特性。当你在使用 Vue.js 为现有标签添加动态行为 (dynamic behavior) 时,v- 前缀很有帮助,然而,对于一些频繁用到的指令来说,就会感到使用繁琐。同时,在构建由 Vue.js 管理所有模板的单页面应用程序 (SPA - single page application) 时,v- 前缀也变得没那么重要了。因此,Vue.js 为 v-bindv-on 这两个最常用的指令,提供了特定简写:

v-bind 缩写:

  1. <!-- 完整语法 -->
  2. <a v-bind:href="url">...</a>
  3. <!-- 缩写 -->
  4. <a :href="url">...</a>

v-on 缩写:

  1. <!-- 完整语法 -->
  2. <a v-on:click="doSomething">...</a>
  3. <!-- 缩写 -->
  4. <a @click="doSomething">...</a>

系统内置指令

v-text

v-html

v-show

v-if

v-else

v-else-if

v-for

v-on

v-bind

v-model

v-pre

v-cloak

总结

  • v-text
    • {{}} 一样的,唯一的区别是
    • {{}} 会造成闪烁问题
    • v-text 不会有闪烁问题
    • 如果还想用 {{}} 又不想有闪烁问题,则使用 v-cloak 来处理
  • v-html
  • v-show
    • 条件显示和隐藏
    • 无论真假,都会渲染显示在 DOM 结构中
    • 条件为真,则让 display 显示
    • 条件为假,则 display 不显示
    • 如果需要频繁的切换显示和隐藏,则使用 v-show
  • v-if
    • 真正的条件渲染
    • 条件为真,则渲染这个 DOM
    • 条件为假,则移除不渲染这个 DOM
    • 如果只是一次显示或隐藏,则建议 v-if
    • v-if 和 v-show
      • 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
  • v-else
  • v-else-if
  • v-for
  • v-on
  • v-bind
  • v-model
  • v-pre
  • v-cloak
    • 如果还想用 {{}} 又不想有闪烁问题,则使用 v-cloak 来处理
    • 在头部加一个特殊的样式:[v-cloak] {display: none;}
    • 然后在被 Vue 管理的模板入口节点上作用 v-cloak 指令
    • 原理:默认一开始被 Vue 管理的模板是隐藏着的,当 Vue 解析处理完 DOM 模板之后,会自动把这个样式去除,然后就显示出来
  • v-once

自定义指令

在 Vue 中除了核心功能默认内置的指令 (例如 v-modelv-show),Vue 也允许注册自定义指令。

有的情况下,我们仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

语法:注册及使用

举个让输入框自动聚焦的例子,如下。

全局注册:

  1. // 注册一个全局自定义指令 `v-focus`
  2. Vue.directive('focus', {
  3. // 当被绑定的元素插入到 DOM 中时……
  4. inserted: function (el) {
  5. // 聚焦元素
  6. el.focus()
  7. }
  8. })

也可以局部注册:

  1. directives: {
  2. focus: {
  3. // 指令的定义
  4. inserted: function (el) {
  5. el.focus()
  6. }
  7. }
  8. }

使用:

  1. <!-- 当页面加载时,该元素将获得焦点 -->
  2. <input v-focus>

注意事项:

  • 在模板中使用自定义指令必须加上 v- 前缀
  • 对于驼峰命名法的自定义指令,在使用的时候使用 - 连接即可。
  • 全局注册的自定义指令可以在任何组件中使用。
  • 组件内注册的自定义指令只能在被该组件管理的模板中使用。

钩子函数

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

下面我们用一个例子来验证文档中的描述:

  1. <div id="app">
  2. <input type="checkbox" v-model="seen">
  3. <h1 v-if="seen" v-demo>{{ message }}</h1>
  4. <input type="text" v-model="message">
  5. </div>
  1. Vue.directive('demo', {
  2. bind(el, binding) {
  3. console.log('binding', el.parentNode) // null
  4. },
  5. inserted: function (el) {
  6. console.log('inserted', el.parentNode) // <div id="app">...</div>
  7. },
  8. update(el, binding) {
  9. console.log('update', el.innerHTML) // 'Hello Vue.js!'
  10. },
  11. componentUpdated(el, binding) {
  12. console.log('componentUpdated', el.innerHTML) // 'Hello'
  13. },
  14. unbind(el, binding) {
  15. console.log('unbind')
  16. }
  17. })
  18. new Vue({
  19. el: '#app',
  20. data: {
  21. seen: true,
  22. message: 'Hello Vue.js!'
  23. }
  24. })

总结:

  • bind 时父节点为 null,inserted 时父节点存在
  • update 和 componentUpdated 就是更新前和更新后的区别
  • unbind 可以做一些收尾工作,例如清除定时器
  • ​文档说的没错 ?

钩子函数参数

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM 。
  • binding 一个对象,包含以下属性:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

!> 除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。

这是一个使用了这些属性的自定义钩子样例:

  1. <div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
  1. Vue.directive('demo', {
  2. bind: function (el, binding, vnode) {
  3. var s = JSON.stringify
  4. el.innerHTML =
  5. 'name: ' + s(binding.name) + '<br>' +
  6. 'value: ' + s(binding.value) + '<br>' +
  7. 'expression: ' + s(binding.expression) + '<br>' +
  8. 'argument: ' + s(binding.arg) + '<br>' +
  9. 'modifiers: ' + s(binding.modifiers) + '<br>' +
  10. 'vnode keys: ' + Object.keys(vnode).join(', ')
  11. }
  12. })
  13. new Vue({
  14. el: '#hook-arguments-example',
  15. data: {
  16. message: 'hello!'
  17. }
  18. })
  1. name: "demo"
  2. value: "hello!"
  3. expression: "message"
  4. argument: "foo"
  5. modifiers: {"a":true,"b":true}
  6. vnode keys: tag, data, children, text, elm, ns, context, fnContext, fnOptions, fnScopeId, key, componentOptions, componentInstance, parent, raw, isStatic, isRootInsert, isComment, isCloned, isOnce, asyncFactory, asyncMeta, isAsyncPlaceholder

函数简写

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

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

对象字面量

如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。

  1. <div v-demo="{ color: 'white', text: 'hello!' }"></div>
  1. Vue.directive('demo', function (el, binding) {
  2. console.log(binding.value.color) // => "white"
  3. console.log(binding.value.text) // => "hello!"
  4. })

自定义指令强化

  • 模拟实现一些内置的系统指令