第 5 章 内置指令

回顾一下第 2 章的内容,我们己经介绍过指令(Directive)的概念了, Vue.js 的指令是带有特殊前缀"v-" 的 HTML 特性, 它绑定一个表达式,并将一些特性应用到 DOM 上。其实我们已经用到过很多 Vue 内置的指令,比如 v-html、 v-pre,还有上一章的 v-bind。本章将继续介绍 Vue.js 中更多常用的内置指令。

基本指令
  • v-cloak

v-cloak 不需要表达式,它会在 Vue 实例结束编译时从绑定的 HTML 元素上移除, 经常和 css 的 display: none;配合使用:

<body>
   <div id="app">
       {{ message }}
   </div>   
</body>
<script>
   var vm = new Vue({    
      el: '#app',
      data: {
          message: '这是一段测试文本'
     }
   })
</script>

这时虽然己经加了指令 v-cloak,但其实并没有起到任何作用,当网速较慢、 Vue.js 文件还没加载完时,在页面上会显示{ { message } }的字样,直到 Vue 创建实例、编译模板时, DOM 才会被替换,所以这个过程屏幕是有闪动的。只要加一句 css 就可以解决这个问题了:

  [v-cloak] {
      display: none;
  }

在一般情况下, v-cloak 是一个解决初始化慢导致页面闪动的最佳实践,对于简单的项目很实用,但是在具有工程化的项目里,比如后面章节中将介绍 webpack 和 vue-router 时,项目的 HTML 结构只有一个空的 div 元素,剩余的内容都是由路由去挂载不同组件完成的,所以不再需要 v-cloak 。

  • v-once

v-once 也是一个不需要表达式的指令,作用是定义它的元素或组件只渲染一次,包括元素或组件的所有子节点。首次渲染后,不再随数据的变化重新渲染,将被视为静态内容,例如:

<body>
   <div id="app">
      <span v-once>{{ message }}</span>
      <div v-once>
          <span>{{ message }}</span>
      </div>      
   </div>   
</body>
<script>
   var vm = new Vue({    
      el: '#app',
      data: {
          message: '这是一段测试文本'
     }
   })
</script>

v-once 在业务中也很少使用,当你需要进一步优化性能时,可能会用到。

条件渲染指令
  • v-if、 v-else-if、 v-else

与 JavaScript 的条件语句 if、 else、 else if 类似, Vue.js 的条件指令可以根据表达式的值在 DOM 中渲染或销毁元素/组件,例如:

<body>
   <div id="app">
      <p v-if="status === 1">当 status 为1时显示该行</p>  
      <p v-else-if="status === 2">当 status 为2时显示该行</p>  
      <p v-else>否则显示该行</p>  
   </div>   
</body>
<script>
   var vm = new Vue({    
      el: '#app',
      data: {
          status: 1
     }
   })
</script>

v-else-if 要紧跟 v-if, v-else 要紧跟 v-else-if 或 v-if,表达式的值为真时, 当前元素/组件及所有子节点将被渲染,为假时被移除。如果一次判断的是多个元素,可以在 Vue.js 内置的<template> 元素上使用条件指令,最终渲染的结果不会包含该元素,例如:

<body>
   <div id="app">
      <template v-if="status === 1">
          <p>这是一段文本</p>
          <p>这是一段文本</p>
          <p>这是一段文本</p>
      </template>
   </div>   
</body>
<script>
   var vm = new Vue({    
      el: '#app',
      data: {
          status: 1
     }
   })
</script>

Vue 在渲染元素时,出于效率考虑,会尽可能地复用已有的元素而非重新渲染, 比如下面的示例:

<body>
   <div id="app">
      <template v-if="type === 'name'">
          <label>用户名:</label>
          <input placeholder="请输入用户名..." />
      </template>
      <template v-else>
          <label>邮箱:</label>
          <input placeholder="请输入邮箱..." />
      </template>
      <button @click="handleToggleClick">切换输入类型</button>
   </div>   
</body>
<script>
   var vm = new Vue({    
      el: '#app',
      data: {
          type: 'name'
      },
      methods: {
          handleToggleClick: function(){
              this.type = this.type === 'name' ? 'mail' : 'name';
          }
      }
   })
</script>

如图所示,键入内容后,点击切换按钮,虽然 DOM 变了,但是之前在输入框键入的内容并没有改变,只是替换了 placeholder 的内容,说明<input>元素被复用了。


切换之前

切换之后

如果你不希望这样做,可以使用 Vue.js 提供的 key 属性,它可以让你自己决定是否要复用元 素, key 的值必须是唯一的,例如:

<body>
   <div id="app">
      <template v-if="type === 'name'">
          <label>用户名:</label>
          <input placeholder="请输入用户名..." key="name-input" />
      </template>
      <template v-else>
          <label>邮箱:</label>
          <input placeholder="请输入邮箱..." key="mail-input" />
      </template>
      <button @click="handleToggleClick">切换输入类型</button>
   </div>   
</body>
<script>
   var vm = new Vue({    
      el: '#app',
      data: {
          type: 'name'
      },
      methods: {
          handleToggleClick: function(){
              this.type = this.type === 'name' ? 'mail' : 'name';
          }
      }
   })
</script>

给两个<input> 元素都增加 key 后, 就不会复用了,切换类型时键入的内容也会被删除,不过 <label>元素仍然是被复用的,因为没有添加 key 属性。

  • v-show

v-show 的用法与 v-if 基本一致,只不过 v-show 是改变元素的 css 属性 display 。当 v-show 表达式的值为 false 时, 元素会隐藏,查看 DOM 结构会看到元素上加载了内联样式 display: none;, 例如:

<body>
   <div id="app">
      <p v-show="status === 1">当 status 为 1 时显示该行</p>
   </div>   
</body>
<script>
   var vm = new Vue({    
      el: '#app',
      data: {
          status: 2
      }
   })
</script>

渲染后的结果为:


提示: v-show 在 <template> 使用

  • v-if 与 v-show 的选择
  • v-if 和 v-show 具有类似的功能,不过 v-if 才是真正的条件渲染,它会根据表达式适当地销毁或重建元素及绑定的事件或子组件。若表达式初始值为 false,则一开始元素/组件并不会渲染,只有当条件第一次变为真时才开始编译。
  • 而 v-show 只是简单的 css 属性切换,无论条件真与否,都会被编译。相比之下, v-if 更适合条件不经常改变的场景,因为它切换开销相对较大,而 v-show 适用于频繁切换条件。
列表渲染指令 v-for
  • 基本用法

当需要将一个数组遍历或枚举一个对象循环显示时,就会用到列表渲染指令 v-for 。它的表达式需结合 in 来使用,类似 item in items 的形式,看下面的示例:

 <body>
     <div id="app">
         <ul>
             <li v-for="book in books">{{book.name}}</li>
         </ul>   
     </div>
 </body>
 <script>
     var vm = new Vue({
         el: '#app',
         data: {
             books: [ 
                 { name: '《Vue.js 实战》' },
                 { name: '《JavaScript 语言精辟》' },
                 { name: '《JavaScript 高级程序设计》' }
             ]
         }
     }); 
 </script>

我们定义一个数组类型的数据 books, 用 v-for 将<li>标签循环渲染, 效果如图所示。



在表达式中, books 是数据, book 是当前数组元素的别名 , 循环出的每个<li>内的元素都可以访问到对应的当前数据 book 。列表渲染也支持用 of 来代替 in 作为分隔符,它更接近 JavaScript 迭代器的语法:

<li v-for="book of books">{{book.name}}</li>

v-for 的表达式支持一个可选参数作为当前项的索引 , 例如:

 <body>
     <div id="app">
         <ul>
             <li v-for="(book,index) in books">{{ index }} - {{book.name}}</li>
         </ul>   
     </div>
 </body>
 <script>
     var vm = new Vue({
         el: '#app',
         data: {
             books: [ 
                 { name: '《Vue.js 实战》' },
                 { name: '《JavaScript 语言精辟》' },
                 { name: '《JavaScript 高级程序设计》' }
             ]
         }
     }); 
 </script>

分隔符 in 前的语句使用括号, 第二项就是 books 当前项的索引,渲染后的结果如图所示。



如果你使用过 Vue.js 1.x 的版本,这里的 index 也可以由内置的 $index 代替, 不过在 2.x 里取消了该用法。
与 v-if 一样, v-for 也可以用在内置标签<template>上, 将多个元素进行渲染:

 <body>
     <div id="app">
         <ul>
             <template v-for="book in books">
              <li>书名:{{ book.name }}</li>
              <li>作者:{{ book.author }}</li>
              <hr />
             </template>
         </ul>   
     </div>
 </body>
 <script>
     var vm = new Vue({
         el: '#app',
         data: {
             books: [ 
                 { 
                  name: '《西游记》',
                  author: '吴承恩'
                 },
                 { 
                  name: '《三国演义》',
                  author: '罗贯中'
                 },
                 { 
                  name: '《水浒传》',
                  author: '施耐庵'
                 },
                 { 
                  name: '《红楼梦》',
                  author: '曹雪芹'
                 }
             ]
         }
     }); 
 </script>

除了数组外, 对象的属性也是可以遍历的,例如:

 <body>
     <div id="app">
         <p v-for="value in user">{{ value }}</p>
     </div>
 </body>
 <script>
     var vm = new Vue({
         el: '#app',
         data: {
             user: {
              name: '清水三千尺',
              gender: '男',
              age: 29
             }
         }
     }); 
 </script>

渲染后的结果为:



遍历对象属性时,有两个可选参数,分别是键名和索引:

 <body>
     <div id="app">
         <ul>
          <li v-for="(value,key,index) in user">
              {{ index }} - {{ key }} : {{ value }}
          </li>
         </ul>
     </div>
 </body>
 <script>
     var vm = new Vue({
         el: '#app',
         data: {
             user: {
              name: '清水三千尺',
              gender: '男',
              age: 29
             }
         }
     }); 
 </script>

渲染后的结果如图所示。



v-for 还可以迭代整数:

 <body>
     <div id="app">
        <span v-for="i in 10">{{ i + ' '}}</span>
     </div>
 </body>
 <script>
     var vm = new Vue({
         el: '#app'
     }); 
 </script>

渲染后的结果为:


  • 数组更新

Vue 的核心是数据与视图的双向绑定,当我们修改数组时, Vue 会检测到数据变化,所以用 v-for 渲染的视图也会立即更新。 Vue 包含了一组观察数组变异的方法,使用它们改变数组也会触 发视图更新:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

例如,我们将之前一个示例的数据 books 添加一项:

     vm.books.push({
      name: '《周易》',
      author: '姬昌'
     });

可以尝试编写完整示例来查看效果。
使用以上方法会改变被这些方法调用的原始数组,有些方法不会改变原数组,例如:

  • filter()
  • concat()
  • slice()

它们返回的是一个新数组,在使用这些非变异方法时,可以用新数组来替换原数组,还是之前展示书目的示例,我们找出含有 JavaScript 关键词的书目,例如:

<body>
    <div id="app">
        <ul>
            <li v-for="(book,index) in books">{{ index }} - {{book.name}}</li>
        </ul>   
    </div>
</body>
<script>
    var vm = new Vue({
        el: '#app',
        data: {
            books: [ 
                { name: '《Vue.js 实战》' },
                { name: '《JavaScript 语言精辟》' },
                { name: '《JavaScript 高级程序设计》' }
            ]
        }
    }); 
    vm.books = vm.books.filter(function(item){
      return item.name.match(/JavaScript/);
    });
</script>

渲染的结果中,第一项《Vue. 实战》被过滤掉了,只显示了书名中含有 JavaScript 的选项。



Vue 在检测到数组变化时,并不是直接重新渲染整个列表,而是最大化地复用 DOM 元素。替换的数组中,含有相同元素的项不会被重新渲染,因此可以大胆地用新数组来替换旧数组,不用担心性能问题。
需要注意的是,以下变动的数组中, Vue 是不能检测到的,也不会触发视图更新:

  • 通过索引直接设置项,比如 vm.books[3] = { ... } 。
  • 修改数组长度,比如 vm.books.length = 1 。

解决第一个问题可以用两种方法实现同样的效果,第一种是使用 Vue 内置的 set 方法:

    Vue.Set(vm.books,3,{
      name: '《CSS 揭秘》',
      author: '[希] Lea Verou '
    });

如果是在 webpack 中使用组件化的方式(在后面的学习中将介绍),默认是没有导入 Vue 的,这时可以使用$set ,例如:

    this.$set(vm.books,3,{
      name: '《CSS 揭秘》',
      author: '[希] Lea Verou '
    });

这里的 this 指向的就是当前组件实例,即 vm 。在非 webpack 模式下也可以用set 方法,例如 app.set( ... )
另一种方法:

    vm.books.splice(3, 1 , { 
      name: '《CSS 揭秘》',
      author: '[希] Lea Verou '
    });

第二个问题也可以直接用 splice 来解决:

vm.books.splice(1);
  • 过滤与排序

当你不想改变原数组,想通过一个数组的副本来做过滤或排序的显示时,可以使用计算属性来返回过滤或排序后的数组,例如:

  <body>
      <div id="app">
          <template v-for="book in filterBooks">
              <li>书名:{{ book.name }}</li>
              <li>作者:{{ book.author }}</li>
          </template>
      </div>
  </body>
  <script>
      var vm = new Vue({
          el: '#app',
          data: {
              books: [
                  {
                      name: '《HTML》',
                      author: '嘻嘻'
                  },
                  {
                      name: '《Java 讲义》',
                      author: '哈哈'
                  },
                  {
                      name: '《Java 核心思想》',
                      author: '呵呵'
                  },
                  {
                      name: '《CSS》',
                      author: '嘎嘎'
                  }
              ]
          },
          computed: {
              filterBooks: function(){
                  return this.books.filter(function(book){
                      return book.name.match(/Java/);
                  });
              }
          }
      });
  </script>


上例是把书名中包含 Java 关键词的数据过滤出来,计算属性 filterBooks 依赖 books,但是不会修改 books。
提示:在 Vue.js 2.x 中废弃了 1.x 中内置的 limitBy、 filterBy 和 orderBy 过滤器,统一改用计算属性来实现。

方法与事件
  • 基本用法

在前面章节中,我们已经引入了 Vue 事件处理的概念 v-on,在事件绑定上,类似原生 JavaScript 的 onclick 等写法,也是在 HTML 上进行监听的。例如,我们监听一个按钮的点击事件,设置一个计数器,每次点击都加 1:

  <body>
      <div id="app">
          点击次数:{{ counter }}
          <button @click="counter++">+1</button>
      </div>
  </body>
  <script>
      var vm = new Vue({
          el: '#app',
          data: {
              counter: 0
          }
      });
  </script>

提示:上面的@click等同于v-on:click 也是一个语法糖, 如不特殊说明, 后面都将使用语法糖写法,可以回顾第 二章中的内容。
@click 的表达式可以直接使用 JavaScript 语句,也可以是一个在 Vue 实例中 methods 选项内的函数名,比如对上例进行扩展,再增加一个按钮,点击一次,计数器加 10:

  <body>
      <div id="app">
          点击次数:{{ counter }}
          <button @click="counter++">+1</button>
          <button @click="handleAdd(10)">+10</button>
      </div>
  </body>
  <script>
      var vm = new Vue({
          el: '#app',
          data: {
              counter: 0
          },
          methods: {
              handleAdd: function(count){
                  count = count || 1;
                  //this 指向当前 Vue 实例 vm
                  this.counter += count;
              }
          }
      });
  </script>

在 methods 中定义了我们需要的方法供 @click 调用, 需要注意的是,@click 调用的方法名后可以不跟括号“()” 。 此时,如果该方法有参数,默认会将原生事件对象 event 传入,可以尝试修改为@click=”handleAdd”,然后在 handleAdd 内打印出 count 参数看看。在大部分业务场景中,如果方法不需要传入参数,为了简便可以不写括号。
这种在 HTML 元素上监听事件的设计看似将 DOM 与 JavaScript 紧藕合,违背分离的原理,实则刚好相反。因为通过 HTML 就可以知道调用的是哪个方法,将逻辑与 DOM 解耦,便于维护。 最重要的是, 当 ViewModel 销毁时,所有的事件处理器都会自动删除,无须自己清理。
Vue 提供了一个特殊变量 $event,用于访问原生 DOM 事件,例如下面的实例可以阻止链接打开:

  <body>
      <div id="app">
          <a href="http://www.baidu.com" @click="handleClick('禁止打开',$event)">点我打开链接</a>
      </div>
  </body>
  <script>
      var vm = new Vue({
          el: '#app',
          data: {
              counter: 0
          },
          methods: {
              handleClick: function(message,event){
                  event.preventDefault();
                  window.alert(message);
              }
          }
      });
  </script>
  • 修饰符

在上例使用的 event.preventDefault() 也可以用 Vue 事件的修饰符来实现,在@绑定的事件后加小圆点“.”,再跟一个后缀来使用修饰符。 Vue 支持以下修饰符:

  • .stop
  • .prevent
  • .capture
  • .self
  • .once

具体用法如下:

  <a href="http://www.baidu.com" @click="handleClick('禁止打开',$event)">点我打开链接</a>
  <!-- 阻止单击事件冒泡 -->
  <a @click.stop="handle"></a>
  <!-- 提交事件不再重载页面 -->
  <form @submit.prevent="handle"></form>
  <!-- 修饰符可以串联 -->
  <a @click.stop.prevent="handle"></a>
  <!-- 只有修饰符 -->
  <form @submit.prevent></form>
  <!-- 添加事件侦听器时使用事件捕获模式 -->
  <div @click.capture="handle">...</div>
  <!-- 只当事件在该元素本身(而不是子元素)触发时触发回调 -->
  <div @click.self="handle">...</div>
  <!-- 只触发一次,组件同样适用 -->
  <div @click.once="handle">...</div>

在表单元素上监听键盘事件时,还可以使用按键修饰符,比如按下具体某个键时才调用方法:

  <!-- 只有在keyCode 是13时调用vm.submit() -->
  <input @keyup.13="submit" />

也可以自己配置具体按键:

  Vue.config.keyCodes.fl = 112; 
  //全局定义后,就可以使用 @keyCodes.fl

除了具体的某个 keyCode 外, Vue 还提供了一些快捷名称,以下是全部的别名:

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

这些按键修饰符也可以组合使用,或和鼠标一起配合使用:

  • .ctrl
  • .alt
  • .shift
  • .meta (Mac 下是 Command 键, Windows 下是窗口键)

例如:

  <!-- Shift + S -->
  <input @keyup.shift.83="handleSave" />
  <!-- Ctrl + Click -->
  <div @click.ctrl="doSomething">Do something</div>

以上就是事件指令 v-on 的基本内容,在后面章节的组件中,我们还将介绍用 v-on 来绑定自定义事件。

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

推荐阅读更多精彩内容