Vue.js 学习笔记 第5章 内置指令

2023-11-12

 

本篇目录:

5.1 基本指令

5.2 条件渲染指令

5.3 列表渲染指令 v-for

5.4 方法与事件

5.5 实战:利用计算属性、指令等知识开发购物车

 

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

5.1 基本指令

5.1.1 v-cloak

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

 1 <div id="app" v-cloak>
 2     {{message}}
 3 </div>
 4 
 5 <script>
 6     var app = new Vue({
 7         el: "#app",
 8         data: {
 9             message: "这是一段文本"
10         }
11     });
12 </script>

 

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

1 <style>
2     [v-cloak] {
3         display: none;
4     }
5 </style>

 

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

5.1.2 v-once

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

 1 <div id="app">
 2     <span v-once>{{message}}</span>
 3     <div v-once>
 4         <span>{{message}}</span>
 5     </div>
 6 </div>
 7 
 8 <script>
 9     var app = new Vue({
10         el: "#app",
11         data: {
12             message: "这是一段文本"
13         }
14     });
15 </script>

 

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

5.2 条件渲染指令

5.2.1 v-if、v-else-of、v-else

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

 1 <div id="app">
 2     <p v-if="status===1">当status为1时显示此行</p>
 3     <p v-else-if="status===2">当status为2时显示此行</p>
 4     <p v-else>否则显示此行</p>
 5 </div>
 6 
 7 <script>
 8     var app = new Vue({
 9         el: "#app",
10         data: {
11             status: 1
12         }
13     });
14 </script>

 

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

 1 <div id="app">
 2     <template v-if="status===1">
 3         <p>这是一段文本</p>
 4         <p>这是一段文本</p>
 5         <p>这是一段文本</p>
 6     </template>
 7 </div>
 8 
 9 <script>
10     var app = new Vue({
11         el: "#app",
12         data: {
13             status: 1
14         }
15     });
16 </script>

 

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

 1 <div id="app">
 2     <template v-if="type==='name'">
 3         <label for="name">用户名:</label>
 4         <input type="text" id="name" name="name" placeholder="请输入用户名">
 5     </template>
 6     <template v-else>
 7         <label for="mail">邮箱:</label>
 8         <input type="text" id="mail" name="mail" placeholder="请输入邮箱">
 9     </template>
10     <button type="button" @click="handleToggleClick">切换输入类型</button>
11 </div>
12 
13 <script>
14     var app = new Vue({
15         el: "#app",
16         data: {
17             type: "name"
18         },
19         methods: {
20             handleToggleClick: function() {
21                 this.type = this.type === "name" ? "mail" : "name";
22             }
23         }
24     });
25 </script>

 

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

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

 1 <div id="app">
 2     <template v-if="type==='name'">
 3         <label for="name">用户名:</label>
 4         <input type="text" id="name" name="name" key="name-input" placeholder="请输入用户名">
 5     </template>
 6     <template v-else>
 7         <label for="mail">邮箱:</label>
 8         <input type="text" id="mail" name="mail" key="mail-input" placeholder="请输入邮箱">
 9     </template>
10     <button type="button" @click="handleToggleClick">切换输入类型</button>
11 </div>
12 
13 <script>
14     var app = new Vue({
15         el: "#app",
16         data: {
17             type: "name"
18         },
19         methods: {
20             handleToggleClick: function() {
21                 this.type = this.type === "name" ? "mail" : "name";
22             }
23         }
24     });
25 </script>

 

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

5.2.2 v-show

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

 1 <div id="app">
 2     <p v-show="status===1">当status为1时显示此行</p>
 3 </div>
 4 
 5 <script>
 6     var app = new Vue({
 7         el: "#app",
 8         data: {
 9             status: 2
10         }
11     });
12 </script>

 

渲染后的结果为:

1 <div id="app">
2     <p style="display: none;">当status为1时显示此行</p>
3 </div>

 

提示:
v-show不能在<template>上使用。

5.2.3 v-if与v-show的选择

v-ifv-show具有类似的功能,不过v-if才是真正的条件渲染,它会根据表达式适当地销毁或重建元素及绑定的事件或子组件。
若表达式初始值为false,则一开始元素/组件并不会渲染,只有当条件第一次变为true时才开始编译。

v-show只是简单的CSS属性切换,无论条件真与否,都会被编译。
相比之下,v-if更适合条件不经常改变的场景,因为它切换开销相对较大,而v-show适用于频繁切换条件。

5.3 列表渲染指令 v-for

5.3.1 基本用法

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

 1 <div id="app">
 2     <ul>
 3         <li v-for="book in books">{{book.name}}</li>
 4     </ul>
 5 </div>
 6 
 7 <script>
 8     var app = new Vue({
 9         el: "#app",
10         data: {
11             books: [
12                 { name: "《Vue.js实战》" },
13                 { name: "《JavaScript语言精粹》" },
14                 { name: "《JavaScript高级程序设计》" }
15             ]
16         }
17     });
18 </script>

 

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

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

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

 

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

 1 <div id="app">
 2     <ul>
 3         <li v-for="(book, index) in books">{{index}} - {{book.name}}</li>
 4     </ul>
 5 </div>
 6 
 7 <script>
 8     var app = new Vue({
 9         el: "#app",
10         data: {
11             books: [
12                 { name: "《Vue.js实战》" },
13                 { name: "《JavaScript语言精粹》" },
14                 { name: "《JavaScript高级程序设计》" }
15             ]
16         }
17     });
18 </script>

 

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

提示:
如果你使用过Vue.js 1.x的版本,这里的index也可以由内置的$index代替,不过在2.x里取消了该用法。

v-if一样,v-for也可以用在内置标签<template>上,将多个元素进行渲染:

 1 <div id="app">
 2     <ul>
 3         <template v-for="book in books">
 4             <li>书名:{{book.name}}</li>
 5             <li>作者:{{book.author}}</li>
 6         </template>
 7     </ul>
 8 </div>
 9 
10 <script>
11     var app = new Vue({
12         el: "#app",
13         data: {
14             books: [
15                 { name: "《Vue.js实战》", author:"梁灏" },
16                 { name: "《JavaScript语言精粹》", author:"Douglas Crockford" },
17                 { name: "《JavaScript高级程序设计》", author:"Nicholas C.Zakas" }
18             ]
19         }
20     });
21 </script>

 

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

 1 <div id="app">
 2     <span v-for="value in user">{{value}}</span>
 3 </div>
 4 
 5 <script>
 6     var app = new Vue({
 7         el: "#app",
 8         data: {
 9             user: {
10                 name: "Aresn",
11                 gender: "",
12                 age: 26
13             }
14         }
15     });
16 </script>

 

渲染后的结果为:

1 <div id="app">
2     <span>Aresn</span>
3     <span></span>
4     <span>26</span>
5 </div>

 

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

 1 <div id="app">
 2     <ul>
 3         <li v-for="(value, key, index) in user">
 4             {{index}} - {{key}} - {{value}}
 5         </li>
 6     </ul>
 7 </div>
 8 
 9 <script>
10     var app = new Vue({
11         el: "#app",
12         data: {
13             user: {
14                 name: "Aresn",
15                 gender: "",
16                 age: 26
17             }
18         }
19     });
20 </script>

 

渲染后的结果如图5-5所示:

v-for还可以迭代整数:

1 <div id="app">
2     <span v-for="n in 10">{{n}} </span>
3 </div>
4 
5 <script>
6     var app = new Vue({
7         el: "#app"
8     });
9 </script>

 

渲染后的结果为:

 1 <div id="app">
 2     <span>1 </span>
 3     <span>2 </span>
 4     <span>3 </span>
 5     <span>4 </span>
 6     <span>5 </span>
 7     <span>6 </span>
 8     <span>7 </span>
 9     <span>8 </span>
10     <span>9 </span>
11     <span>10 </span>
12 </div>

 

5.3.2 数组更新

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

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

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

1 app.books.push({
2     name: "《CSS解密》",
3     author: "[希] Lea Verou"
4 });

 

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

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

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

 1 <div id="app">
 2     <ul>
 3         <template v-for="book in books">
 4             <li>书名:{{book.name}}</li>
 5             <li>作者:{{book.author}}</li>
 6         </template>
 7     </ul>
 8 </div>
 9 
10 <script>
11     var app = new Vue({
12         el: "#app",
13         data: {
14             books: [
15                 { name: "《Vue.js实战》", author:"梁灏" },
16                 { name: "《JavaScript语言精粹》", author:"Douglas Crockford" },
17                 { name: "《JavaScript高级程序设计》", author:"Nicholas C.Zakas" }
18             ],
19         }
20     });
21     
22     app.books = app.books.filter(function(item) {
23         return item.name.match(/JavaScript/);
24     });
25 </script>

 

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

Vue在检测到数组变化时,并不是直接重新渲染整个列表,而是最大化地复用DOM元素。
替换的数组中,含有相同元素的项不会被重新渲染,因此可以大胆地用新数组来替换就数组,不用担心性能问题。

需要注意的是,以下变动的数组中,Vue是不能检测到的,也不会触发视图更新:

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

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

1 Vue.set(app.books, 2, {
2     name: "《CSS揭秘》",
3     author: "[希] Lea Verou"
4 });

 

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

1 this.$set(app.books, 2, {
2     name: "《CSS揭秘》",
3     author: "[希] Lea Verou"
4 });

 

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

另一种方法:

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

 

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

1 app.books.splice(1);

 

5.3.3 过滤与排序

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

 1 <div id="app">
 2     <ul>
 3         <template v-for="book in filterBooks">
 4             <li>书名:{{book.name}}</li>
 5             <li>作者:{{book.author}}</li>
 6         </template>
 7     </ul>
 8 </div>
 9 
10 <script>
11     var app = new Vue({
12         el: "#app",
13         data: {
14             books: [
15                 { name: "《Vue.js实战》", author:"梁灏" },
16                 { name: "《JavaScript语言精粹》", author:"Douglas Crockford" },
17                 { name: "《JavaScript高级程序设计》", author:"Nicholas C.Zakas" }
18             ],
19         },
20         computed: {
21             filterBooks: function () {
22                 return this.books.filter(function (book) {
23                     return book.name.match(/JavaScript/);
24                 });
25             }
26         }
27     });
28 </script>

 

上例是把书名中包含"JavaScript"关键词的数据过滤出来,计算属性filterBooks依赖books,但是不会修改books
实现排序也是类似的,比如在此基础上新加一个计算属性sortedBooks,按照书名的长度由长到短进行排序:

1 computed: {
2     sortedBooks: function () {
3         return this.books.sort(function (a, b) {
4             return a.name.length < b.name.length;
5         });
6     }
7 }

 

提示:
在Vue.js 2.x中废弃了1.x中内置的limitByfilterByorderBy过滤器,统一改用计算属性来实现。

5.4 方法与事件

5.4.1 基本用法

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

 1 <div id="app">
 2     点击次数: {{counter}}
 3     <button type="button" @click="counter++">累加1</button>
 4 </div>
 5 
 6 <script>
 7     var app = new Vue({
 8         el: "#app",
 9         data: {
10             counter: 0
11         }
12     });
13 </script>

 

提示:
上面的@click等同于v-on:click,是一个语法糖,如不特殊说明,后面都将使用语法糖写法,可以回顾第2.3章节。

@click的表达式可以直接使用JavaScript语句,也可以是一个在Vue实例中methods选项内的函数名。
比如对上例进行扩展,再增加一个按钮,点击一次,计数器累加10:

 1 <div id="app">
 2     点击次数: {{counter}}
 3     <button type="button" @click="handleAdd()">累加1</button>
 4     <button type="button" @click="handleAdd(10)">累加10</button>
 5 </div>
 6 
 7 <script>
 8     var app = new Vue({
 9         el: "#app",
10         data: {
11             counter: 0
12         },
13         methods: {
14             handleAdd: function(count) {
15                 count = count || 1;
16                 // this指向当前Vue实例app
17                 this.counter += count;
18             }
19         }
20     });
21 </script>

 

methods中定义了我们需要的方法供@click调用,需要注意的是,@click调用的方法名后可以不跟括号()
此时,如果该方法有参数,默认会将原生事件对象event传入,可以尝试修改为@click="handleAdd",然后在handleAdd内打印出count参数看看。
在大部分业务场景中,如果方法不需要传入参数,为了渐变可以不写括号。

这种在HTML元素上监听事件的设计看似将DOM与JavaScript紧耦合,违背分离的原理,实则刚好相反。
因为通过HTML就可以知道调用的是哪个方法,讲逻辑与DOM接口,便于维护。
最重要的是,当ViewModel销毁时,所有的事件处理器都会自动删除,无需自己清理。

Vue提供了一个特殊变量$event,用于访问原生DOM事件,例如下面的实例可以阻止连接打开:

 1 <div id="app">
 2     <a href="http://www.baidu.com" @click="handleClick('禁止打开', $event)">打开连接</a>
 3 </div>
 4 
 5 <script>
 6     var app = new Vue({
 7         el: "#app",
 8         methods: {
 9             handleClick: function(message, event) {
10                 event.preventDefault();
11                 alert(message);
12             }
13         }
14     });
15 </script>

 

5.4.2 修饰符

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

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

具体用法如下:

 1 <!-- 阻止事件冒泡 -->
 2 <a @click.stop="handle"></a>
 3             
 4 <!-- 提交事件不再重载页面 -->
 5 <form @submit.prevent="handle"></form>
 6             
 7 <!-- 修饰符可以串联 -->
 8 <a @click.stop.prevent="handle"></a>
 9             
10 <!-- 只有修饰符 -->
11 <form @submit.prevent></form>
12             
13 <!-- 添加事件侦听器时使用事件捕获模式 -->
14 <div @click.capture="handle">...</div>
15             
16 <!-- 只当事件在该元素本身(而不是子元素)触发时触发回调 -->
17 <div @click.self="handle">...</div>
18             
19 <!-- 只触发一次,组件同样适用 -->
20 <div @click.once="handle">...</div>

 

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

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

 

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

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

 

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

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

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

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

例如:

1 <!-- Shift + S -->
2 <input @keyup.shift.83="handleSave">
3             
4 <!-- Ctrl + Click -->
5 <div @click.ctrl="doSomething">Do Smothing</div>

 

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

5.5 实战:利用计算属性、指令等知识开发购物车

前五章内容基本涵盖了Vue.js最核心的常用的知识点,掌握这些内容已经可以上手开发一些小功能了。
本节则以Vue.js的计算属性、内置指令、方法等内容为基础,完成一个在业务中具有代表性的小功能:购物车。

在开始写代码前,要对需求进行分析,这样有助于我们理清业务逻辑,尽可能还原设计产品交互。

购物车需要展示一个已加入购物车的商品列表,包含商品名称、商品单价、购买数量和操作等信息,还需要实时显示购买的总价。
其中购买数量可以增加或减少,每类商品还可以从购物车中移除。
最终实现的效果如图5-6所示:

在明确需求后,我们就可以开始编程了。
因为业务代码较多,这次我们将HTML、CSS、JavaScript分离为3个文件,便于阅读和维护:

  • index.html (引入资源及模板)
  • index.js (Vue实例及业务代码)
  • index.css (样式)

现在index.html中引入Vue.js和相关资源,创建一个根元素来挂在Vue实例:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3     <head>
 4         <meta charset="UTF-8">
 5         <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6         <meta http-equiv="X-UA-Compatible" content="ie=edge">
 7         <title>购物车示例</title>
 8         <link rel="stylesheet" href="index.css">
 9     </head>
10     <body>
11         <div id="app" v-cloak></div>
12 
13         <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
14         <script src="index.js"></script>
15     </body>
16 </html>

 

注意,这里将vue.min.jsindex.js文件写在<body>的最底部,如果写在<head>里,Vue实例将无法创建,因为此时DOM还没有被解析完成,除非通过异步或在事件DOMContentLoaded(IE是onreadystatechange)触发时再创建Vue实例,这有点像jQuery的$(document).ready()方法。

本例需要用到Vue.js的computedmethods等选项,在index.js中县初始化实例:

1 var app = new Vue({
2     el: "#app",
3     data: {},
4     computed: {},
5     methods: {}
6 });

 

我们需要的数据比较简单,只有一个列表,里面包含了商品名称、单价、购买数量。
在实际业务中,这个列表应该是通过Ajax从服务端动态获取的,这里只做示例,所以直接写入在data选项内,另外每个商品还应该有一个全局唯一的id
我们在data内写入列表list

1 data: {
2     list: [
3         {id: 1, name: "iPhone 7", price: 6188, count: 1}, 
4         {id: 2, name: "iPad Pro", price: 5888, count: 1}, 
5         {id: 3, name: "McaBook Pro", price: 21488, count: 1}
6     ]
7 }

 

数据构建好后,可以在index.html中展示列表了,毫无疑问,肯定会用到v-for,不过在此之前,我们先做一些小的优化。
因为每个商品都是可以从购物车移除的,所以当列表为空时,在页面中显示一个”购物车为空“的提示更为友好,我们可以通过判断数组list的长度来实现该功能:

1 <div id="app" v-cloak>
2     <template v-if="list.length"></template>
3     <div v-else>购物车为空</div>
4 </div>

 

<template>里的代码分两部分,一部分是商品列表信息,我们用表格<table>来展现;
另一部分就是带有千位分隔符的商品总价(每隔三位数加一个逗号)。
这部分代码如下:

 1 <template v-if="list.length">
 2     <table>
 3         <thead>
 4             <tr>
 5                 <th></th>
 6                 <th>商品名称</th>
 7                 <th>商品单价</th>
 8                 <th>购买数量</th>
 9                 <th>操作</th>
10             </tr>
11         </thead>
12         <tbody>
13         </tbody>
14     </table>
15     <div>总价:¥ {{totalPrice}}</div>
16 </template>

 

总价totalPrice是依赖于商品列表而动态变化的,所以我们用计算属性来实现,顺便将结果转换为带有"千位分隔符"的数字。
index.jscomputed选项内写入:

 1 computed: {
 2     totalPrice: function () {
 3         var total = 0;
 4         for (var i = 0; i < this.list.length; i++) {
 5             var item = this.list[i];
 6             total += item.price * item.count;
 7         }
 8         return total.toString().replace(/\B(?=(\d{3})+$)/g, ",");
 9     }
10 }

 

这段代码难点在于千位分隔符的转换,大家可以查阅正则匹配的相关内容后尝试了解replace()的正则含义。

最后就剩下商品列表的渲染和相关的几个操作了。
现在<body>内把数组listv-for指令循环出来:

 1 <tbody>
 2     <tr v-for="(item, index) in list">
 3         <td>{{index + 1}}</td>
 4         <td>{{item.name}}</td>
 5         <td>{{item.price}}</td>
 6         <td>
 7             <button @click="handleReduce(index)" :disabled="item.count===1">-</button>
 8             {{item.count}}
 9             <button @click="handleAdd(index)">+</button>
10         </td>
11         <td>
12             <button @click="handleRemove(index)">移除</button>
13         </td>
14     </tr>
15 </tbody>

 

商品序号、名称、单价、数量都是直接使用插值来完成的,在第4列的两个按钮<button>用于增/减购买数量,分别绑定了两个方法handleReducehandleAdd,参数都是当前商品在数组list中的索引。
很多时候,一个元素上会同时使用多个特性(尤其是在组件中使用props传递数据时),写在一行代码较长,不便阅读,所以建议特性过多时,将每个特性都单独写为一行,比如第一个<button>中使用了v-bindv-on两个指令(这里都用的语法糖写法)。
每件商品购买数量最少是1件,所以当count为1时,不允许再继续减少,所以这里给<button>动态绑定了disabled特性来禁用按钮。

index.js中继续完成剩余的3个方法:

 1 methods: {
 2     handleReduce: function (index) {
 3         if (this.list[index].count === 1) return;
 4         this.list[index].count--;
 5     },
 6     handleAdd: function (index) {
 7         this.list[index].count++;
 8     },
 9     handleRemove: function (index) {
10         this.list.splice(index, 1);
11     }
12 }

 

这3个方法都是直接对数组list的操作,没有太复杂的逻辑。
需要说明的是,虽然在<button>上已经绑定了disabled特性,但是在handleReduce方法内有判断了以便,这是因为在某些时刻,可能不一定会用<button>元素,也可能是div、span等,给它们增加disabled是没有任何作用的,所以安全起见,在业务逻辑中在判断一次,避免因修改HTML模板后出现bug。

一下是购物车示例的完整代码:
index.html:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3     <head>
 4         <meta charset="UTF-8">
 5         <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6         <meta http-equiv="X-UA-Compatible" content="ie=edge">
 7         <title>购物车示例</title>
 8         <link rel="stylesheet" href="index.css">
 9     </head>
10     <body>
11         <div id="app" v-cloak>
12             <template v-if="list.length">
13                 <table>
14                     <thead>
15                         <tr>
16                             <th></th>
17                             <th>商品名称</th>
18                             <th>商品单价</th>
19                             <th>购买数量</th>
20                             <th>操作</th>
21                         </tr>
22                     </thead>
23                     <tbody>
24                         <tr v-for="(item, index) in list">
25                             <td>{{index + 1}}</td>
26                             <td>{{item.name}}</td>
27                             <td>{{item.price}}</td>
28                             <td>
29                                 <button @click="handleReduce(index)" :disabled="item.count===1">-</button>
30                                 {{item.count}}
31                                 <button @click="handleAdd(index)">+</button>
32                             </td>
33                             <td>
34                                 <button @click="handleRemove(index)">移除</button>
35                             </td>
36                         </tr>
37                     </tbody>
38                 </table>
39                 <div>总价:¥ {{totalPrice}}</div>
40             </template>
41             <div v-else>购物车为空</div>
42         </div>
43 
44         <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
45         <script src="index.js"></script>
46     </body>
47 </html>

 

index.js:

 1 var app = new Vue({
 2     el: "#app",
 3     data: {
 4         list: [
 5             {id: 1, name: "iPhone 7", price: 6188, count: 1}, 
 6             {id: 2, name: "iPad Pro", price: 5888, count: 1}, 
 7             {id: 3, name: "McaBook Pro", price: 21488, count: 1}
 8         ]
 9     },
10     computed: {
11         totalPrice: function () {
12             var total = 0;
13             for (var i = 0; i < this.list.length; i++) {
14                 var item = this.list[i];
15                 total += item.price * item.count;
16             }
17             return total.toString().replace(/\B(?=(\d{3})+$)/g, ",");
18         }
19     },
20     methods: {
21         handleReduce: function (index) {
22             if (this.list[index].count === 1) return;
23             this.list[index].count--;
24         },
25         handleAdd: function (index) {
26             this.list[index].count++;
27         },
28         handleRemove: function (index) {
29             this.list.splice(index, 1);
30         }
31     }
32 });

 

index.css:

1 [v-cloak]{display:none;}
2 table{border:1px solid #E9E9E9; border-collapse:collapse; border-spacing:0; empty-cells:show;}
3 th, td{padding:8px 16px; border:1px solid #E9E9E9; text-align:left;}
4 th{background:#F7F7F7; color:#5C6B77; font-weight:600; white-space:nowrap;}

 

练习1:在当前示例基础上扩展商品列表,新增一项是否选中该商品的功能,总价变为只计算选中商品的总价,同时提供一个全选的按钮。
练习2:将商品列表list改为一个二维数组来实现商品的分类,比如可分为"电子产品"、"生活用品"和"果蔬",同类商品聚合在一起。提示,你可能会用到两次v-for

 

转载于:https://www.cnblogs.com/geeksss/p/10776465.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Vue.js 学习笔记 第5章 内置指令 的相关文章

  • 使用 获取用于 javascript 的 RSA 密钥?

    我的 Web 项目需要一个 RSA 密钥对 虽然有一些库 但我认为依靠浏览器 为了安全性和速度 为我生成密钥是个好主意 是否可以使用注册机或其他浏览器 API 来执行此操作 我不知道如何从注册机获取密钥 它们似乎是在提交时生成的 但我不想将
  • 缓存 firestore 中 get 的第一个实现

    我希望 firestore 每次都先从缓存中获取数据 根据 Firestore 文档 传递 缓存 或 服务器 选项必须启用相同的功能 下面的例子 db collection cities where capital true get cac
  • Google Charts(AreaChart)如何检测缩放变化

    我正在画一个面积图 在覆盖层上有一些标记 我正在使用explorer选项 仅限水平 以便用户放大和缩小 问题是我找不到一种方法来通知缩放更改 以便有机会更新制造商位置 有一个图表范围变化事件 但它不是由 AreaChart 触发的 我尝试检
  • ngx-DataTable 对列进行排序无法正常工作 Angular 4

    虽然我对角度非常陌生 但我在使用 ngx DataTable 时遇到了一些困难 我使用简单的 ngx DataTable 进行简单的操作 问题出在列上 尽管我已将 attr 声明为 sortable true 但排序不起作用 这是代码 表定
  • 动态表中每个按钮的 Jquery-Ui 对话框表单

    我正在生成一个 HTML 表 每行都有一个按钮 必须打开 Jquery ui 对话框表单 The table table class table table reporting table condensed table striped t
  • 夜间值班。单击带有文本的元素

    我遇到问题 无法单击具有某些独特文本的网页元素 我有这样的结构 div class wg wagon type title Text div 我试试这个 click wg wagon type title contains Text 但我有
  • 如何为 HTML5 音频元素制作加载栏?

    我正在尝试为 HTML5 音频元素制作一个加载栏 显示加载 缓冲的百分比 对于视频标签 可以使用以下方法进行计算 video buffered end 0 video duration 但我无法让它与音频标签一起使用 它只是返回一个固定值
  • 如何在提交表单之前删除自动数字格式?

    我正在使用 jQuery 插件自动数字 http www decorplanit com plugin 但是当我提交表单时 我无法删除之前字段上的格式POST 我尝试使用 input autonumeric destroy 和其他方法 但它
  • 卸载/销毁 Angular 延迟加载组件

    我的设置与此处找到的帖子类似http ify io lazy loading in angularjs http ify io lazy loading in angularjs 处理 Angular 中我的应用程序的各种组件的延迟加载 我
  • 单击输入字段会触发窗口调整大小

    我有一个带有徽标 菜单和搜索的标题 当我在桌面上时 我会按该顺序显示所有元素 但如果我的窗口宽度小于 980 像素 菜单会隐藏 有一个切换按钮 并且徽标会与nav并附在徽标之后 如果宽度更大 则徽标将再次分离并附加到 DOM 中的旧位置 w
  • 离子旋转器未显示

    我用 http 请求填充 Ionic 集合重复列表 但我不想将所有内容直接加载到 DOM 中 因此 我只显示其中一些项目 并在您向下滚动时添加其余项目 为此我实现了无限滚动功能 当我到达页面底部时 它应该显示一个旋转器 但它没有 这些物品至
  • EJS在JS onload函数中访问express变量

    我知道你可以像这样获取 ejs 文件中变量的值 h1 h1 如果我要在同一个 ejs 页面的 onload javascript 函数中使用相同的标题变量 我将如何使用它 例如 这个函数产生一个控制台错误说 未捕获的语法错误 意外的标识符
  • 哪些网络浏览器不支持 Javascript?以及如何识别客户端使用的是哪个浏览器?

    是否有不支持 javascript 的网络浏览器 以及如何确定客户端是否正在使用这些浏览器之一 或者客户端禁用了javascript 是否有不支持 javascript 的网络浏览器 当然 Lynx http en wikipedia or
  • 更改时触发跨度文本/html

    jQuery 或 JavaScript 中是否有任何事件在以下情况下触发span标签 text html 已更改 Code span class user location span user location change functio
  • 在 jQuery AJAX 成功中从 MySql 获取特定响应

    好吧 我有这个 ajax 代码 它将在 Success 块中返回 MySql 的结果 ajax type POST url index php success function data alert data My Query sql SE
  • 如何将当前元素传递给 Knockout.js 绑定中的 Javascript 函数?

    因此 我尝试根据是否选中子复选框 使用 Knockout js 将类添加到元素 为此 我试图通过this作为我的函数的参数 目前 我的精简 DOM 结构如下 tr td td tr
  • toLocaleDateString() 在 Chrome 中如何工作?

    我理解了javascript方法toLocaleDateString 使用的计算机设置 让我们来W3Schools 示例 http www w3schools com jsref tryit asp filename tryjsref to
  • 如何修复 getImageData() 错误画布已被跨源数据污染?

    我的代码在本地主机上运行得很好 但在网站上却不起作用 我从控制台收到此错误 对于这一行 getImageData x y 1 1 data Uncaught SecurityError Failed to execute getImageD
  • Javascript:更改输入值时设置光标位置

    当您输入公式时 我试图在我的应用程序中重现类似于 Microsoft Excel Google Sheets 的用户体验 并且您可以使用不同的公式和变量来自动完成下拉菜单 为此 在验证自动完成功能后 我希望能够控制光标的位置 例如 如果我输
  • 数字和小数的输入掩码

    在测试我的程序后 我发现了以下错误 我在 sqlserver 中的表包含 价格数字 6 2 我的程序的用户输入价格 555 00 就很好了 但是当他输入 555555 时 这是错误的 所以我需要指定掩码 其中尾数是可选的 0 到 999 小

随机推荐

  • 数据挖掘算法基础-关联规则

    数据挖掘中 被常拿来说的啤酒尿布的例子就是一个很典型的运用关联算法来做购物来分析的例子 常被用于交易数据 关系数据的分析 发现数据集中隐藏的频繁模式 这些频繁模式可以用关联规则的形式表示 有效的关联规则对商家的商品进出货摆放都有很大的指导意
  • 直方图均衡化与直方图规定化

    一 认识图像 当我们面对图像的时候 我们面对的是抽象的矩阵 如下图 下面是0 255的灰度图像的表示 密密麻麻的 那么我们做的直方图 其实就是对这些像素值的统计 如下图所示 其中Bin表示条数 数据和范围是对图的解释 二 为什么要做直方图均
  • qt 嵌入web页面_Qt -在应用程序中嵌入Web内容之环境搭建

    一 Qt应用程序与Web结合的发展 1 从Qt5 5开始 Qt WebKit模块被废弃了 取而代之的是Qt WebEngine模块 当时可以使用该模块将应用程序与Web技术结合 2 Qt WebEngine模块提供了一个Web浏览器引擎 可
  • ChatGPT:概述Vue.js中data函数初始化和created钩子函数调用的顺序和问题解决方法

    ChatGPT 概述Vue js中data函数初始化和created钩子函数调用的顺序和问题解决方法 我将输入一段Vue代码 请你记住 created console log this queryInfo this getClueList
  • Libuv源码分析 —— 6. 事件循环【uv_run】

    通过之前的学习 咱们已经明白了在事件循环中的三个核心内容 分别是 Libuv源码分析 定时器 Libuv源码分析 idle prepare check Libuv源码分析 poll io 现在让咱们从头捋一遍事件循环到底完成了什么功能呢 u
  • scrapy里面的response.xpath(“用xpath插件找打的路径“)返回值为空?

    response xpath 用xpath插件找打的路径 返回值为空 1 可能是因为路径是有问题的 2 可能是start urls的路径是有问题的 可以从network中找找路径 复制一下
  • 使用vant2问题整理

    1 export createVNode imported as createVNode was not found in vue possible exports EffectScope computed customRef defaul
  • C++11移动语义解析

    当给函数传递对象当做函数参数时 可以使用引用类型来减少拷贝对象的代价 尤其是避免容器的拷贝等 但是当把函数内的局部对象当做返回值时 我们无法返回该局部对象的引用 导致每次返回局部对象都会进行拷贝 因为返回局部对象的引用是无意义的 当函数调用
  • 编译原理实验日志

    编译原理 生成四元式 实验原理 构造SLR 1 分析表 调试过程 实验原理 构造SLR 1 分析表 首先求得follow集 follow E follow T follow F 画出DFA状态转换图 调试过程 没有判断 因为字符串中没有表示
  • dubbo优雅停机

    dubbo优雅停机 Dubbo是通过JDK的ShutdownHook来完成优雅停机的 所以如果用户使用 kill 9 PID 等强制关闭指令 是不会执行优雅停机的 只有通过 kill PID 时 才会执行 原理 服务提供方 停止时 先标记为
  • grafana与prometheus实现监控可视化

    1 Grafana基础知识 Grafana是一个开源的指标监测和可视化工具 官方网站为 Grafana The open observability platform Grafana Labs 常用于展示基础设施的时序数据和应用程序运行分析
  • 大话设计模式9—观察者模式(通知者与观察者)

    大话设计模式9 观察者模式 老板回来 我不知道 1 需求 老板回来 我不知道 2 双向耦合的设计 2 1 前台秘书类 2 2 看股票同事类 2 3main函数及输出 3 解耦修改 3 1 抽象观察者类 3 2 前台秘书类 3 3 main函
  • Java编程中出现乱码的原因

    乱码的原因 理解了编码 我们来看乱码 乱码有两种常见原因 一种比较简单 就是简单的解析错误 另外一种比较复杂 在错误解析的基础上进行了编码转换 我们分别介绍 1 解析错误 看个简单的例子 一个法国人采用Windows 1252编码写了个文件
  • Windows/PC(win + R) 电脑常见操作命令50条

    摘要 win R 1 cmd 打开终端 2 gpedit msc 本地组策略编辑器 3 Nslookup IP地址侦测器 4 explorer 文件资源管理器 5 notepad 系统默认记事本 6 cleanmgr 磁盘清理 7 serv
  • 微信公众号运营错误的四个方式

    1 很多广告宣传 很多的微信公众平台注册便是为了更好地宣传策划商品 在开展內容輸出的情况下 沒有立在客户的视角开展內容輸出 消息推送的內容可能是七拼八凑 与微信公众号的精准定位偏移很远 要想取得成功的运营公众号 最先要做的便是深层次发掘总体
  • vue3 + vite 在线预览docx, pdf, pptx(内外网)并实现移动端适配

    一 内网 1 docx 使用docx preview 安装插件 npm i docx preview S 引入依赖 docx import renderAsync from docx preview let docx import meta
  • GO终端读取

    GO终端读取 Go语言获取标准输入 Go语言 fmt 包下有 fmt Scan fmt Scanf fmt Scanln 三个函数 可以在程序运行过程中获取用户输入 func Scan a interface n int err error
  • Spring核心之一:IOC

    IOC Inversion of Control 其实是一种思想 这种思想并不是Spring独有的 而是在软件开发中 大家提出的一种开发原则 类似面向接口编程原则 开闭原则等 网上有很多类似的文章尝试去通俗易懂地解释IOC思想 这里我根据自
  • BugkuCTF-Crypto题小山丘的秘密

    本题考查希尔密码 解题流程 题目信息 1 根据提示知道是希尔 hill 密码 解密网站 www atoolbox net Tool php Id 914 ac csdn flag txt 里给出A 1 一般的希尔密码是A 0 B 1 C 2
  • Vue.js 学习笔记 第5章 内置指令

    本篇目录 5 1 基本指令 5 2 条件渲染指令 5 3 列表渲染指令 v for 5 4 方法与事件 5 5 实战 利用计算属性 指令等知识开发购物车 回顾一下第2 2节 我们己经介绍过指令 Directive 的概念了 Vue js的指