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


    本篇目录:

    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

  • 相关阅读:
    连续3年!SpreadJS 纯前端表格控件荣获“中国优秀软件产品”
    终于有一款组件可以全面超越Apache POI
    List<Object> 多条件去重
    xml文档的解析并通过工具类实现java实体类的映射:XML工具-XmlUtil
    soap get/post请求
    map转java对象
    springboot postman 对象里传时间格式问题
    spring boot的多环境部署
    Hibernate 之 @Query查询
    利用maven命令将外部jar包导进maven仓库
  • 原文地址:https://www.cnblogs.com/geeksss/p/10776465.html
Copyright © 2020-2023  润新知