8月:Vue.js实战

目录

 

第一章 初识Vue.js

1.1   Vue.js是什么

1.2    如何使用Vue.js

第二章 数据绑定和第一个Vue应用

2.1 Vue实例与数据绑定

2.2 指令与事件

2.3 语法糖

第三章 计算属性

3.1 计算属性用法

3.2 计算属性缓存

第四章 b-bind及class与style绑定

4.1 了解v-bind

4.2 绑定class 的几种方式

4.3 绑定内联样式

第五章 内置指令

5.1 基本指令

5.2 条件渲染指令

5.3 列表渲染指令v-for

5.4 方法与事件

5.5 利用计算属性、指令等开发购物车

第六章 表单与v-model

6.1 基本用法

6.2 修饰符

第七章 组件详解

7.1 组件与复用

7.2 使用props传递数据

7.3 组件通信

7.4 使用slot分发内容

7.5 组件高级用法

7.6 其他

7.7 开发一个数字输入框组件

第八章 自定义指令

8.1 基本用法

8.2 开发一个可从外部关闭的下拉菜单


第一章 初识Vue.js

1.1   Vue.js是什么

1.1.1 MVVM模式

Vue.js在设计上使用MVVM模式,MVVM模式是由经典的软件架构MVC衍生而来的,当View(视图层)变化时,会自动更新到ViewModel(视图模型),View和ViewModel之间通过双向绑定建立联系。

1.1.2 Vue.js有什么不同

Vue.js通过MVVM的模式拆分为视图与数据两部分,并将其分离,只需要关心数据,Vue会自动搞定DOM。

1.2    如何使用Vue.js

Vue.js是一个渐进式的JavaScript框架(所谓渐进式,是指可以一步一步、有阶段性的来使用Vue.js,不必一开始就使用所有的东西)。

引入Vue.js框架后,在body底部使用new Vue()的方式创建一个实例。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <ul>
        <li v-for="book in books">{{book.name}}</li>
    </ul>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    new Vue({
        el:'#app',
        data:{
            books:[
                {name:'《vue.js实战》'},
                {name:'《JavaScript》权威指南'}
            ]
        }
    })
</script>
</body>
</html>

对于一些业务逻辑复杂,对前端工程有要求的项目,可以使用Vue单文件的形式配合webpack使用,必要时还会用到vuex来管理状态,vue-router来管理路由。

第二章 数据绑定和第一个Vue应用

2.1 Vue实例与数据绑定

2.1.1 实例与数据

通过构造函数Vue可以创建一个Vue的根实例,并启动Vue应用:

var app = new Vue({

//选项

})
  1. el:必不可少,el用于指定一个页面中已存在的DOM元素来挂载Vue实例,它可以是HTMLElement,也可以是CSS选择器。挂载成功后可以通过app.$el来访问该元素,
  2. data:通过Vue的data选项可以声明应用内需要双向绑定的数据,所有会用到的数据尽量都预先在data内声明,这样不至于将数据散落在业务逻辑中。data还可以指向一个已有的变量,并且它们之间默认建立了双向绑定,当修改其中任意一个时,另一个也会一起变化。

注:v-model指令的值对应于Vue实例的data选项中的name字段。

2.1.2 生命周期

每个Vue实例创建时,都会经历一系列的初始化过程,同时也会调用相应的生命周期的钩子。可以利用这些钩子,在合适的时机执行业务逻辑。

Vue的生命周期钩子:

  1. created:实例创建完成后调用,此阶段完成了数据的观测等,但尚未挂载,$el还不可以用,可用于初始化处理一些数据。
  2. mounted:el挂载到实例上后调用,一般第一个业务逻辑会从这里开始。
  3. beforeDestroy:实例销毁之前调用,主要解绑一些使用addEventListener监听事件等。

这些钩子也可以作为选项写入Vue实例内,并且钩子的this指向的是调用它的Vue实例。

2.1.3 插值与表达式

“{{}}”双大括号是最基本的文本插值方法,它会自动将双向绑定的数据实时显示出来。

如果想输出HTML而不是将数据解释后的纯文本, 可以使用v-html指令。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <span v-html="link"></span>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            //link的内容会被渲染为一个具有点击功能的a标签,而不是纯文本
            link:'<a href="#">这是一个链接</a>'
        }
    })
</script>
</body>
</html>

注:如果将用户产生的内容使用v-html输出后,有可能导致XSS攻击,所以要再服务端对用户提交的内容进行处理,一般可以将尖括号“<>”转义。

如果想显示{{}}标签,而不进行替换,使用v-pre即可跳过这个元素和它的子元素的编译过程。

<span v-pre>{{ 这里的内容是不被编译}}</span>

在{{}}中还可以使用JavaScript表达式进行简单的运算、三元运算等。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    {{ number / 10 }}
    {{ isOK ? '确定' : '取消' }}
    {{ text.split(',').reverse().join(',') }}
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            number:110,
            isOK:false,
            text:'123,456'
        }
    })
</script>
</body>
</html>

Vue.js只支持单个表达式,不支持语句和流控制(控制语句的开始、结束,程序的运行),在表达式中,不能使用用户自定义的全局变量,只能使用Vue白名单内的全局变量。

2.1.4 过滤器

Vue.js支持在{{}}插值的尾部添加一个管道符“|”对数据进行过滤,经常用于格式化文本。过滤的规则是自定义的,通过给Vue实例添加选项filters来设置。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    {{ date | formateDate }}
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    //在月份、日期、小时等小于10时前补零
    var padDate = function (value) {
        return value < 10 ? '0' + value : value ;
    };
    var app = new Vue({
        el:'#app',
        data:{
            date:new Date()
        },
        filters:{
            formateDate:function (value) {//value为需要过滤的数据
                var date = new Date(value);
                var year = date.getFullYear();
                var month = padDate(date.getMonth()+1);
                var day = padDate(date.getDay());
                var hours = padDate(date.getHours());
                var minutes = padDate(date.getMinutes());
                var seconds = padDate(date.getSeconds());
                //返回整理好的数据
                return year + '-' + month + '-' + day + ' ' + hours + ":" + minutes + ":" + seconds;
            }
        },
        mounted:function () {
            var _this = this;//声明一个变量指向Vue实例this,保证作用域一致
            this.timer = setInterval(function () {
                _this.date = new Date();//修改数据date
            },1000);
        },
        beforeDestory:function () {
            if (this.timer){
                clearInterval(this.timer);//在Vue实例销毁前,清除定时器
            }
        }
    })
</script>
</body>
</html>

过滤器也可以串联,而且可以接收参数。

<!--串联-->
{{ message | filterA | filterB }}
<!--接收参数,字符串arg1和arg2将分别传给过滤器的第二个和第三个参数,因为第一个是数据本身-->
{{ message | filter('arg1', 'arg2')}}

2.2 指令与事件

指令带有前缀v-,指令的主要职责是当其表达式的值改变时,相应的将某些行为应用到DOM上。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <p v-if="show">显示这段文本</p>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            show:true
        },
    })
</script>
</body>
</html>

数据驱动DOM是Vue.js的核心理念。

(1)v-bind的基本用途是动态更新HTML元素上的属性,比如id、class等。

链接地址与图片地址都与数据进行了绑定,当通过各种方式改变数据时,链接和图片都会自动更新

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <a v-bind:href="url">链接地址</a>
    <img v-bind:src="imgurl">
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            url:'https://www.baidu.com',
            imgurl:'http://xxx.xxx.xx/img.jpg'
        },
    })
</script>
</body>
</html>

(2)v-on:用来绑定事件监听器。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <p v-if="show">显示这段文本</p>
    <!-- v-on:click绑定一个点击事件-->
    <button v-on:click="handleClose">点击隐藏</button>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            show:true
        },
        methods:{
            handleClose:function () {
                this.show = false;
            }
        }
    })
</script>
</body>
</html>

表达式可以是一个方法名,这些方法否写在Vue实例的methods属性内,并且是函数的形式,函数内的this指向的是当前Vue实例本身,因此可以直接使用this.xxx的形式来访问或修改数据。表达式也可以是一个内联语句,

注:如果要绑定的事件要处理复杂的业务逻辑,建议在methods里声明一个方法,这样可读性更强更好维护。

2.3 语法糖

语法糖是指在不影响功能的情况下,添加某种方法实现同样的效果。

  1. v-bind可以省略v-bind,直接写一个冒号“:”.
  2. v-on可以直接用“@”来缩写。

第三章 计算属性

所有的计算属性都以函数的形式写在Vue实例内的computed选项内,最终返回计算的结果。遇到复杂的逻辑时应该使用计算属性。

3.1 计算属性用法

计算属性可以依赖多个Vue实例的数据,只要其中任一数据变化,计算属性就会重新执行,视图也会更新。

当package1或package2中的商品有任何变化,比如购买数量变化或删除商品,计算属性prices就会自动更新,视图中的总价就会自动变化
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    总价:{{prices}}
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            package1:[
                {
                    name:'iPhone X',
                    price:6999,
                    count:2
                },
                {
                    name:'xiaomi',
                    price:2999,
                    count:1
                }
            ],
            package2:[
                {
                    name:'huawei',
                    price:3999,
                    count:1
                },
                {
                    name:'oppo',
                    price:4999,
                    count:2
                }
            ]
        },
        computed:{
            prices:function () {
                var prices = 0;
                for (var i = 0; i < this.package1.length; i++){
                    prices += this.package1[i].price * this.package1[i].count;
                }
                for (var i = 0; i < this.package2.length; i++){
                    prices += this.package2[i].price * this.package2[i].count;
                }
                return prices;
            }
        }
    })
</script>
</body>
</html>

计算属性还经常用于动态的设置元素的样式名称class和内联样式style。当使用组件时,计算属性也经常用来动态传递props。

计算属性可以依赖其他计算属性。

计算属性不仅可以依赖当前Vue实例的数据,还可以依赖其他实例的数据。

在app2的计算属性reversedText中,依赖的是app1的数据text,所以当text变化时,实例app2的计算属性也会变化

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app1"></div>
<div id="app2">
    {{reversedText}}
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app1',
        data:{
            text:'123,456'
        }
    })
    var app2 = new Vue({
        el:'#app2',
        computed:{
            reversedText:function () {
                //依赖实例1的数据text
                return app1.text.split(',').reverse().join(',');
            }
        }
    })
</script>
</body>
</html>

3.2 计算属性缓存

计算属性是基于它的依赖缓存的。一个计算属性所依赖的数据发生变化时,它才会重新取值。

调用methods里面的方法也可以与计算属性起到同样的作用,methods只要重新渲染,它就会被调用,因此函数也会被执行。

使用计算属性还是methods取决于是否需要缓存,当遍历大数组和做大量计算时,应当使用计算属性。

第四章 b-bind及class与style绑定

4.1 了解v-bind

v-bind主要用法是动态更新HTML元素上的属性。

4.2 绑定class 的几种方式

4.2.1 对象语法

给v-bind:class设置一个对象,可以动态的切换class。

类名active依赖于数据isActive,当其为true时,div会拥有类名active,为false时则没有

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <div :class="{ 'active': isActive }"></div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            isActive:true
        }
    })

</script>
</body>
</html>

对象中也可以传入多个属性,来动态切换class。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <div class="static" :class="{ 'active': isActive, 'error': isError}"></div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            isActive:false,
            isError:true
        }
    })
</script>
</body>
</html>

当:class的表达式过长或逻辑复杂时,还可以绑定一个计算属性。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <div :class="classes"></div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            isActive:true,
            isError:null
        },
        computed:{
            classes:function () {
                return {
                    active: this.isActive && !this.error,
                    'text-fail': this.error && this.error.type === 'fail'
                }
            }
        }
    })
</script>
</body>
</html>

还可以直接绑定一个Object类型的数据,或者使用类似计算属性的methods。

4.2.2 数组语法

当需要应用多个class时,可以使用数组语法,给:class绑定一个数组,应用一个class列表。

也可以使用三元表达式来根据条件切换class。

样式btn始终会应用,当数据size不为空时,会应用样式前缀btn-,后加size值,当数据为disabled为真时,会应用样式disabled

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <button :class="classes"></button>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            size:'large',
            disabled:true
        },
        computed:{
            classes:function () {
                return [
                    'btn',{
                        ['btn-' + this.size]: this.size !== '',
                        ['btn-disabled']:this.disabled
                    }
                ];
            }
        }
    })
</script>
</body>
</html>

4.2.3 在组件上使用

如果直接在自定义组件上使用class或:class,样式规则会直接应用到这个组件的根元素上。然后在调用这个组件时,应用对象语法或数组语法给组件绑定class。这种用法仅适用于自定义组件的最外层是一个根元素,否则会无效,当不满足这种条件或需要给具体的子元素设置类名时,应当使用组件的props来传递。

4.3 绑定内联样式

使用v-bind:style(即:style)可以给元素绑定内联样式,也有对象语法和数组语法。

使用:style时,Vue.js会自动给特殊的CSS属性名称增加前缀,比如transform。

第五章 内置指令

Vue.js的指令是带有特殊前缀“v-”的HTML特性,它绑定一个表达式,并将一些特性应用到DOM上。

5.1 基本指令

5.1.1 v-cloak

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app" v-cloak>
    {{message}}
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            message:'这是一段文本'
        }
    })
</script>
</body>
</html>

在一般情况下,v-cloak是一个解决初始化慢导致页面闪动的最佳实践。

5.1.2 v-once

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app" >
    <span v-once>{{ message }}</span>
    <div v-once>{{ message }}</div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            message:'这是一段文本'
        }
    })
</script>
</body>
</html>

5.2 条件渲染指令

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

Vue.js的条件指令可以根据表达式的值在DOM中渲染或销毁元素/组件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<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>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            status:1
        }
    })
</script>
</body>
</html>

v-else-if要紧跟v-if,v-else要紧跟v-else-if或v-if,如果一次判断的是多个元素,可以在Vue.js内置的<template>元素上使用条件指令,最终渲染的结果不会包含该元素。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app" >
    <template v-if="status===1">
    <p>这是一段文本</p>
    <p>这是一段文本</p>
    <p>这是一段文本</p>
    </template>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            status:1
        }
    })
</script>
</body>
</html>

Vue在渲染元素时,出于效率考虑,会尽可能的复用已有的元素而非重新渲染。

键入内容后,点击切换按钮,虽然DOM变了,但是之前在输入框键入的内容并没有改变,只是替换了placeholder的内容,说明<input>被复用了
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<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>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            type:name
        },
        methods:{
            handleToggleClick:function () {
                this.type = this.type === 'name' ? 'mail' : 'name';
            }
        }
    })
</script>
</body>
</html>

可以使用Vue.js提供的key属性,它可以决定是否要复用元素,key的值必须是唯一的。

给两个<input>元素都增加key之后,就不会复用了,切换类型时键入的内容也会被删除
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<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>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            type:name
        },
        methods:{
            handleToggleClick:function () {
                this.type = this.type === 'name' ? 'mail' : 'name';
            }
        }
    })
</script>
</body>
</html>

5.2.2 v-show

v-show是改变元素的CSS属性display。当v-show表达式的值为false时,元素会隐藏,查看DOM结构会看到元素上加载了内联样式display:none。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app" >
    <p v-show="status===1">当status为1时显示该行</p>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            status:2
        },
    })
</script>
</body>
</html>

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

5.2.3 v-show和v-if的选择

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

v-show只是简单的CSS属性切换,无论条件真与否,都会被编译。

v-if更适合条件不经常改变的场景,因为它切换开销相对较大,v-show适用于频繁切换条件。

5.3 列表渲染指令v-for

5.3.1 基本用法

当需要将一个数组遍历或枚举一个对象循环显示时,就会用到列表渲染指令v-for。它的表达式需要结合in使用。

books是数据,book是当前元素的别名,循环出的每个<li>内的元素都可以访问到对应的当前数据book

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app" >
    <ul>
        <li v-for="book in books">{{ book.name }}</li>
    </ul>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            books:[
                {name:'《vue.js实战》'},
                {name:'《JavaScript》权威指南'}
                ]
        }
    })
</script>
</body>
</html>

注:渲染列表也支持用of来代替in作为分隔符。

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app" >
    <ul>

<!--分隔符前的语句使用括号,第二项就是books,当前项是索引-->
        <li v-for="(book, index) in books">{{index}}-{{ book.name }}</li>
    </ul>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app1 = new Vue({
        el:'#app',
        data:{
            books:[
                {name:'《vue.js实战》'},
                {name:'《JavaScript》权威指南'}
            ]
        }
    })
</script>
</body>
</html>

v-for也可以用在内置标签<template>上。

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

v-for还可以迭代整数。

5.3.2 数组更新

Vue.js包含了一组观察数组变异的方法,使用它们改变数组也会出发视图更新:push()、pop()、shift()、unshift()、splice()、sort()、reverse()。

不会改变原数组的方法:filter()、concat()、slice()。它们返回的是一个新数组,在使用这些非变异方法时,可以用新数组来替换原数组。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app" >
    <ul>
        <template v-for="book in books">
            <li>书名:{{book.name}}</li>
            <li>作者:{{book.author}}</li>
        </template>
    </ul>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            books:[
                {
                    name:'《vue.js实战》',
                    author:'梁灏'
                },
                {
                    name:'《JavaScript高级程序设计》',
                    author:'Nicholas C.Zakas'
                }
                ]
        }
    });
        app.books = app.books.filter(function (item) {
            return item.name.match(/JavaScript/);
    });

</script>
</body>
</html>

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

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

  1. 通过索引直接设置项,比如app.books[3] = {...}。可以使用Vue内置的set方法或者直接用splice来解决这个问题。
  2. 修改数组长度,比如app.books.length = 1。

5.3.3 过滤与排序

可以使用计算属性来返回过滤或排序后的数组。

计算属性filterBooks依赖books,但是不会修改books

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app" >
    <ul>
        <template v-for="book in filterBooks">
            <li>书名:{{book.name}}</li>
            <li>作者:{{book.author}}</li>
        </template>
    </ul>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            books:[
                {
                    name:'《vue.js实战》',
                    author:'梁灏'
                },
                {
                    name:'《JavaScript高级程序设计》',
                    author:'Nicholas C.Zakas'
                }
                ]
        },
        computed:{
            filterBooks:function () {
                return this.books.filter(function (book) {
                    return book.name.match(/JavaScript/);
                });
            }
        }
    })

</script>
</body>
</html>

按照书名的长度由长到短排序

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app" >
    <ul>
        <template v-for="book in sortedBooks">
            <li>书名:{{book.name}}</li>
            <li>作者:{{book.author}}</li>
        </template>
    </ul>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            books:[
                {
                    name:'《vue.js实战》',
                    author:'梁灏'
                },
                {
                    name:'《JavaScript高级程序设计》',
                    author:'Nicholas C.Zakas'
                },
                {
                    name:'《JavaScript语言精粹》',
                    author:'Douglas Crockford'
                }
                ]
        },
        computed:{
            sortedBooks:function () {
                return this.books.sort(function (a, b) {
                    return a.name.length < b.name.length;
                });
            }
        }
    })

</script>
</body>
</html>

5.4 方法与事件

5.4.1 基本用法

监听一个按钮的点击事件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app" >
    点击次数:{{ counter }}
    <button @click="counter++">+1</button>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            counter:0
        }
    })

</script>
</body>
</html>

@click的表达式可以直接使用JavaScript语句,也可以是一个Vue实例中methods选项内的函数名。

上例中增加一个按钮

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app" >
    点击次数:{{ counter }}
    <button @click="handleAdd">+1</button>
    <button @click="handleAdd(10)">+10</button>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            counter:0
        },
        methods:{
            handleAdd:function (count) {
                count = count || 1;
                this.counter += count;
            }
        }
    })

</script>
</body>
</html>

@click调用的方法名后可以不跟括号,此时如果该方法有参数,默认会将原生事件对象event传入。

Vue提供了一个特殊变量$event,用于访问原生DOM事件。

5.4.2 修饰符

Vue支持一下修饰符:.stop、.prevent、.capture、.self、.once。

<!--阻止单击事件冒泡-->
 <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">

也可以配置具体按键:

<!--全局定义后,就可以使用@keyup.f1-->
 Vue.config.keyCodes.f1 = 112;

Vue提供的快捷名称:.enter、.tab、.delete(捕获删除和退格键)、.esc、.space、.up、.down、.left、.right。这些按键修饰符也可以组合使用,或和鼠标一起配合使用:.ctrl、.alt、.shift、.meta。

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

5.5 利用计算属性、指令等开发购物车

5.5.1 需求

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

5.5.2 实现代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>使用vue.js实现购物车功能</title>
    <style>
        [v-cloak]{
            display: none;
        }
        table{
            border: 1px solid #e9e9e9;
            border-collapse: collapse;
            border-spacing: 0;
            empty-cells: show;
        }
        th,td{
            padding: 8px 16px;
            border: 1px solid #e9e9e9;
            text-align: left;
        }
        th{
            background: #f7f7f7;
            color:#5c6b77;
            font-weight: 600;
            white-space: nowrap;
        }
    </style>
</head>
<body>
<div id="app" v-cloak>
    <template v-if="list.length">
        <table>
            <thead>
            <tr>
                <th>商品名称</th>
                <th>商品价格</th>
                <th>商品数量</th>
                <th>操作</th>
            </tr>
            </thead>
            <tbody>
            <tr v-for="(item,index) in list">
                <td>{{ index +1 }}</td>
                <td>{{ item.name }}</td>
                <td>{{ item.price }}</td>
                <!--增减数量-->
                <td>
                    <button @click="handleReduce(index)" :disable="item.count===1">-</button>
                    {{ item.count }}
                    <button @click="handleAdd(index)">+</button>
                </td>
                <td>
                    <button @click="handleRemove(index)">移除</button>
                </td>
            </tr>
            </tbody>
        </table>
        <div>总价:¥ {{ totalPrice }}</div>
    </template>
    <!--通过判断数组list的长度来实现当列表为空时,在页面显示“购物车为空”的提示-->
    <div v-else>购物车为空</div>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            list:[
                {
                    id:1,
                    name:'apple x',
                    price:6999,
                    count:1
                },
                {
                    id:2,
                    name:'oppo',
                    price:4999,
                    count:1
                },
                {
                    id:3,
                    name:'xiaomi',
                    price:1999,
                    count:1
                }
            ]
        },
        computed:{
            totalPrice:function () {
                var total = 0;
                for (var i = 0; i < this.list.length; i++){
                    var item = this.list[i];
                    total += item.price * item.count;
                }
                //使用正则表达式转换千位分隔符
                return total.toString().replace(/\B(?=(\d{3})+$)/g,',');
            }
        },
        methods:{
            handleReduce:function (index) {
                if(this.list[index].count===1) return;
                this.list[index].count--;
            },
            handleAdd:function (index) {
                this.list[index].count++;
            },
            handleRemove:function (index) {
                this.list.splice(index,1);
            }
        }
    })

</script>
</body>
</html>

第六章 表单与v-model

6.1 基本用法

Vue.js提供了v-model指令,用于在表单类元素上双向绑定数据。

在输入框输入的同时,{{ message }}也会实时将内容渲染在视图中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <input type="text" v-model="message" placeholder="请输入">
    <p>输入的内容为:{{ message }}</p>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            message:''
        }
    })

</script>
</body>
</html>

使用v-model后,表单控件显示的值只依赖所绑定的数据,不再关心初始化时value属性。使用v-model时,在拼音阶段,Vue是不会更新数据的,当敲下汉字才会触发更新。如果要总是实时更新,可以用@input来替代v-model。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <input type="text" @input="handleInput" placeholder="请输入">
    <p>输入的内容为:{{ message }}</p>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            message:''
        },
        methods:{
            handleInput:function (e) {
                this.message = e.target.value;
            }
        }
    })

</script>
</body>
</html>

6.1.1 单选按钮

单选按钮在单独使用时,不需要v-model,直接使用v-bind绑定一个布尔类型的值,为真时选中,为否时不选。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <input type="radio" :check="picked">
    <label>单选按钮</label>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            picked:true
        },
    })

</script>
</body>
</html>

如果是组合使用来实现互斥选择的效果,需要v-modle配合value使用。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <input type="radio" v-model="picked" value="html" id="html">
    <label for="html">HTML</label>
    <br>
    <input type="radio" v-model="picked" value="js" id="js">
    <label for="js">JavaScript</label>
    <br>
    <input type="radio" v-model="picked" value="css" id="css">
    <label for="css">CSS</label>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            picked:'js'
        },
    })

</script>
</body>
</html>

6.1.2 复选框

复选框单独使用时,用v-model来绑定一个布尔值。

在勾选时,数据checked的值变为了true,label中渲染的内容也会更新。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <input type="checkbox" v-model="checked" id="checked">
    <label for="checked">选择状态:{{ checked }}</label>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            checked:false
        }
    })

</script>
</body>
</html>

组合使用时,使用v-model和value一起,多个勾选框都绑定到同一个数组类型的数据,value的值在数组当中,就会选中这一项,这一过程是双向的,在勾选时,value的值也会自动push到这个数组中。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <input type="checkbox" v-model="checked" value="html" id="html">
    <label for="html">HTML</label>
    <br>
    <input type="checkbox" v-model="checked" value="js" id="js">
    <label for="js">JavaScript</label>
    <br>
    <input type="checkbox" v-model="checked" value="css" id="css">
    <label for="css">CSS</label>
    <p>选择的项是:{{ checked }}</p>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            checked:['html']
        },
    })

</script>
</body>
</html>

6.1.3 选择列表

单选:

<option>是备选项,如果含有value属性,v-model就会优先匹配value的值,如果没有,就会直接匹配<option>的text。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <select v-model="selected">
        <option>html</option>
        <option>js</option>
        <option>css</option>
    </select>
    <p>选择的项是:{{ selected }}</p>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            selected:''
        }
    })

</script>
</body>
</html>

给<select>添加multiple属性可以实现多选:

<select v-model="selected" multiple>

使用v-for动态输出<option>:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <select v-model="selected">
        <option v-for="option in options" :value="option.value">{{ option.text }}</option>
    </select>
    <p>选择的项是:{{ selected }}</p>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            selected:'html',
            options:[
                {
                    text:'HTML',
                    value:'html'
                },
                {
                    text:'JavaScript',
                    value:'js'
                },
                {
                    text:'CSS',
                    value:'css'
                }
            ]
        }
    })

</script>
</body>
</html>

可以使用v-bind实现绑定一个动态的数据。

6.2 修饰符

v-model的修饰符用于控制数据同步的时机。

6.2.1 .lazy

在输入框中,v-model默认是在input事件中同步输入框的数据,使用修饰符.lazy会转变为在change时间中同步。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <input type="text" v-model.lazy="message">
    <p>{{ message }}</p>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            message:''
        }
    })
</script>
</body>
</html>

6.2.2 .number

使用修饰符.number可以将输入转换为Number类型,否则输入的是数字,但它的类型其实是String。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <input type="number" v-model.number="message">
    <p>{{ typeof message }}</p>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            message:123
        }
    })
</script>
</body>
</html>

6.2.3 .trim

修饰符.trim可以自动过滤输入的首尾空格

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <input type="text" v-model.trim="message">
    <p>{{ message }}</p>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            message:''
        }
    })
</script>
</body>
</html>

第七章 组件详解

7.1 组件与复用

7.1.1 组件的用法

组件需要注册后才可以使用。注册有全局注册和局部注册两种方式。全局注册后,任何Vue实例都可以使用。全局注册示例:

//my-component为注册的组件自定义标签名称

Vue.component('my-component',{
    //选项
})

要在父实例中使用这个组件,必须要在实例创建前注册,之后就可以用<my-component></my-component>的形式来使用组件。

<div id="app">

    <my-component></my-component>

</div>

<script>

Vue.component('my-component',{
    //选项
});
var app = new Vue({
    el:'#app'
});

</script>

在组件选项中添加template可以显示组件内容。template的DOM结构必须被一个元素包含。

Vue.component('my-component',{
    template:'<div>这里是组件内容</div>'
});

在Vue实例中,使用components选项可以在局部注册组件,注册后的组件只有在该实例作用域下有效。组件中也可以使用components选项来注册组件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <my-component></my-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
var Child = {
    template:'<div>局部注册组件的内容</div>'
};
    var app = new Vue({
        el:'#app',
        components:{
            'my-component':Child
        }
    })
</script>
</body>
</html>

注:Vue组件的模板在某些情况下会受到HTML的限制,这种情况下,可以使用特殊的is属性来挂载组件。

data选项必须是函数,然后将数据return出去。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <my-component></my-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
    template:'<div>{{ message }}</div>',
    data:function () {
        return{
            message:'组件内容'
        }
    }
});
    var app = new Vue({
        el:'#app'
    })
</script>
</body>
</html>

注:JavaScript对象是引用关系,所以如果return出的对象引用了外部的一个对象,那这个对象就是共享的,任何一方修改都会同步。

7.2 使用props传递数据

7.2.1 基本用法

组件不仅仅是要把模板的内容进行复用,更重要的是组件之间要进行通信。通常父组件的模板中包含子组件,父组件要正向的向子组件传递数据或参数,子组件接收到后根据参数的不同来渲染不同的内容或执行操作。这个正向传递数据的过程是通过props来实现的。

在组件中,使用选项props来声明需要从父级接收的数据,props的值可以是两种,一种是字符串数组,一种是对象。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <my-component message="来自父组件的数据"></my-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
    props:['message'],
    template:'<div>{{ message }}</div>',
});
    var app = new Vue({
        el:'#app'
    })
</script>
</body>
</html>

props中声明的数据与组件data函数return的数据组要的区别是props的来自父级,而data中的是组件自己的数据,作用域是组件本身,这两种数据都可以在模板template及计算属性computed和方法methods中使用。在组件的自定义标签上直接写该props的名称,如果要传递多个数据,在props数组中添加即可。

可以使用指令v-model来动态绑定props的值,当父组件的数据变化时,也会传递给子组件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <input type="text" v-model="parentMessage">
    <my-component :message="parentMessage"></my-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
    props:['message'],
    template:'<div>{{ message }}</div>',
});
    var app = new Vue({
        el:'#app',
        data:{
            parentMessage:''
        }
    })
</script>
</body>
</html>

7.2.2 单向数据流

Vue 2.x通过props传递数据是单向的,也就是父组件数据变化时会传递给子组件,但是反过来不行。

7.2.3 数据验证

当props需要验证时,需要对象写法。验证的type类型可以是:String、Number、Boolean、Object、Array、Function。type也可以是一个自定义构造器,使用instanceof检测。

7.3 组件通信

组件关系可以分为父子组件通信、兄弟组件通信、跨级组件通信。

7.3.1 自定义事件

当组件需要向父组件传递数据时,需要用到自定义事件。v-on可以用于组件之间的自定义事件。子组件用$emit()来触发事件,父组件用$on()监听子组件事件。父组件也可以直接在子组件的自定义标签上使用v-on来监听子组件触发的自定义事件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <p>总数:{{ total }}</p>
    <my-component @increase="handleGetTotal" @reduce="handleGetTotal"></my-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
    template:'\
    <div>\
        <button @click="handleIncrease">+1</button>\
    <button @click="handleReduce">-1</button>\
</div>',
    data:function () {
        return{
            counter:0
        }
    },
    methods:{
        handleIncrease:function () {
            this.counter++;
            this.$emit('increase',this.counter);
        },
        handleReduce:function () {
            this.counter--;
            this.$emit('reduce',this.counter);
        }
    }
});
    var app = new Vue({
        el:'#app',
        data:{
            total:0
        },
        methods:{
            handleGetTotal:function (total) {
                this.total = total;
            }
        }
    })
</script>
</body>
</html>

v-on也可以监听DOM事件,用.native修饰符表示监听的是一个原生事件,监听的是该组件的根元素。

7.3.2 使用v-model

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <p>总数:{{ total }}</p>
    <my-component v-model="total"></my-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
    template:'<button @click="handleClick">+1</button>',
    data:function () {
        return{
            counter:0
        }
    },
    methods:{
        handleClick:function () {
            this.counter++;
            this.$emit('input',this.counter);
        }
    }
});
    var app = new Vue({
        el:'#app',
        data:{
            total:0
        }
    })
</script>
</body>
</html>

v-model可以用来创建自定义的表单输入组件,进行数据双向绑定。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <p>总数:{{ total }}</p>
    <my-component v-model="total"></my-component>
    <button @click="handleReduce">-1</button>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
Vue.component('my-component',{
    props:['value'],
    template:'<input :value="value" @input="updateValue">',
    methods:{
        updateValue:function (event) {
            this.$emit('input',event.target.value);
        }
    }
});
    var app = new Vue({
        el:'#app',
        data:{
            total:0
        },
        methods:{
            handleReduce:function () {
                this.total--;
            }
        }
    })
</script>
</body>
</html>

实现一个具有双向绑定的v-model组件要满足以下两个要求:

  1. 接收一个value属性。
  2. 在有新的value时触发input时间。

7.3.3 非父子组件通信

非父子组件一般有两种:兄弟组件和跨级组件。

(1)中央时间总线bus

首先创建一个名为bus的空Vue实例,然后全局定义组件component-a,最后创建Vue实例app,在app初始化时,也就是生命周期mounted钩子函数里监听了来自bus的时间on-message,而在组件component-a中,点击按钮会通过bus把时间on-message发出去,此时app就会收到来自bus的时间,进而在回调里完成自己的业务逻辑。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <p>{{ message }}</p>
    <component-a></component-a>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var bus = new Vue();
    Vue.component('component-a',{
    template:'<button @click="handleEvent">传递事件</button>',
    methods:{
        handleEvent:function () {
            bus.$emit('on-message','来自组件component-a的内容');
        }
    }
});
    var app = new Vue({
        el:'#app',
        data:{
            message:''
        },
        mounted:function () {
            var _this = this;
            bus.$on('on-message',function (msg) {
                _this.message = msg;
            });
        }
    })
</script>
</body>
</html>

(2)父链

在子组件中,使用this.$parent可以直接访问该组件的父实例或组件,父组件也可以通过this.$children访问它所有的子组件,而且可以递归向上或 向下无限访问,直到根实例或最内层的组件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <p>{{ message }}</p>
    <component-a></component-a>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    Vue.component('component-a',{
    template:'<button @click="handleEvent">通过父链直接修改数据</button>',
    methods:{
        handleEvent:function () {
            this.$parent.message = '来自组件component-a的内容';
        }
    }
});
    var app = new Vue({
        el:'#app',
        data:{
            message:''
        }
    })
</script>
</body>
</html>

注:在业务中,子组件应该尽量避免依赖父组件的数据,更不应该去主动修改它的数据,因为这样使得父子组件紧耦合。父子组件最好还是通过props和$emit来通信。

(3)子组件索引

可以用特殊属性ref来为子组件指定一个索引名称。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <button @click="handleRef">通过ref获取子组件实例</button>
    <component-a ref="comA"></component-a>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    Vue.component('component-a',{
    template:'<div>子组件</div>',
    data:function () {
        return{
            message:'子组件内容'
        }
    }
});
    var app = new Vue({
        el:'#app',
        methods:{
            handleRef:function () {
                var msg = this.$refs.comA.message;
                console.log(msg);
            }
        }
    })
</script>
</body>
</html>

在父组件模板中,子组件标签上使用ref指定一个名称,并在父组件内通过this.$refs来访问指定名称的组件。

7.4 使用slot分发内容

7.4.1 什么是slot

当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到slot,这个过程叫做内容分发。

props传递数据、events触发时间和slot分发内容构成了Vue组件的3个API来源。

7.4.2 作用域

父组件模板的内容是在父组件作用域内编译,子组件模板的内容是在子组件作用域内编译。

slot分发的内容,作用域是在父组件上。

7.4.3 slot用法

(1)单个slot

在子组件内使用特殊的<slot>元素就可以为这个子组件开启一个slot,在父组件模板里,插入在子组件标签内的所有内容将替代子组件的<slot>标签及它的内容。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <child-component>
        <p>分发的内容</p>
        <p>更多分发的内容</p>
    </child-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    Vue.component('child-component',{
    template:'\
    <div>\
    <slot>\
    <p>如果父组件没有插入内容,我将作为默认出现</p>\
    </slot>\
    </div>\
    ',
});
    var app = new Vue({
        el:'#app',
    })
</script>
</body>
</html>

注:子组件<slot>内的备用内容,它的作用域是子组件本身。

(2)具名slot

给<slot>元素指定一个name后可以分发多个内容,具名slot可以与单个slot共存。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <child-component>
        <h1 slot="header">标题</h1>
        <p>正文内容</p>
        <p>更多的正文内容</p>
        <div slot="footer">底部信息</div>
    </child-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    Vue.component('child-component',{
        template:'\
    <div class="container">\
    <div class="header">\
    <slot name="header"></slot>\
    </div>\
    <div class="main">\
    <slot></slot>\
    </div> \
    <div class="footer">\
    <slot name="footer"></slot>\
    </div>\
    </div>',
    });
    var app = new Vue({
        el:'#app',
    })
</script>
</body>
</html>

没有name属性的<slot>将作为默认slot出现,父组件没有使用slot特性的元素与内容都将在这里出现。如果没有指定默认的匿名slot,父组件内做鱼的内容片断都将被抛弃。

7.4.4 作用域插槽

作用域插槽是一种特殊的slot,使用一个可以复用的模板替换已渲染的元素。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <child-component>

<!--props只是一个临时变量,template内可以通过临时变量props访问来自子组件插槽的数据-->
        <template scope="props">
            <p>来自父组件的内容</p>
            <p>{{ props.msg }}</p>
        </template>
    </child-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    Vue.component('child-component',{
        template:'\
    <div class="container">\
    <slot msg="来自子组件的内容"></slot>\
    </div>',
    });
    var app = new Vue({
        el:'#app',
    })
</script>
</body>
</html>

使用作用域插槽实现列表组件,允许组件自定义应该如何渲染列表每一项。

子组件my-list接收一个来自父级prop数组books,并且将它在name为book的slot上使用v-for指令循环,同时暴露一个变量bookName。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <my-list :books="books">
        <template slot="book" scope="props">
            <li>{{ props.bookName }}</li>
        </template>
    </my-list>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    Vue.component('my-list',{
        props:{
            books:{
                type:Array,
                default:function () {
                    return [];
                }
            }
        },
        template:'\
    <ul>\
    <slot name="book"\
    v-for="book in books"\
    :book-name="book.name">\
    </slot>\
    </ul>',
    });
    var app = new Vue({
        el:'#app',
        data:{
            books:[
                {name:'《Vue.js实战》'},
                {name:'《JavaScript语言精粹》'}
            ]
        }
    })
</script>
</body>
</html>

作用域插槽的使用场景是既可以复用子组件的slot,又可以使slot内容不一致。

7.4.5 访问slot

Vue.js 2.x提供了用来访问被slot分发的内容的方法$slots,通过$slots可以访问某个具名slot。slot主要用于独立组件开发中。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <child-component>
        <h1 slot="header">标题</h1>
        <p>正文内容</p>
        <p>更多的正文内容</p>
        <div slot="footer">底部信息</div>
    </child-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    Vue.component('child-component',{
        template:'\
        <div class="container">\
        <div class="header">\
        <slot name="header"></slot>\
        </div>\
        <div class="main">\
        <slot></slot>\
        </div>\
        <div class="footer">\
        <slot name="footer"></slot>\
        </div>\
        </div>',
        mounted:function () {
            var heander = this.$slots.header;
            var main = this.$slots.main;
            var footer = this.$slots.footer;
            console.log(footer);
            console.log(footer[0].elm.innerHTML);
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            books:[
                {name:'《Vue.js实战》'},
                {name:'《JavaScript语言精粹》'}
            ]
        }
    })
</script>
</body>
</html>

7.5 组件高级用法

7.5.1 递归组件

组件在它的模板内可以递归的调用自己,只要给组件设置name的选项就可以了。设置name后,在组件模板内就可以递归使用了,不过必须要给一个条件来限制递归的数量,否则会抛出错误。组件递归使用可以用来开发一些具有未知层级关系的独立组件,比如级联选择器和树形控件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <child-component :count="1">
    </child-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    Vue.component('child-component',{
        name:'child-component',
        props:{
            count:{
                type:Number,
                default:1
            }
        },
        template:'\
        <div class="child">\
        <child-component\
        :count="count+1"\
        v-if="count<3"></child-component>\
        </div>',
    });
    var app = new Vue({
        el:'#app',
    })
</script>
</body>
</html>

7.5.2 内联模板

组件的模板一般都是在template选项内定义的,Vue提供了一个内联模板的功能,在使用组件时,给组件标签使用inline-template特性,组件就会把它的内容当做模板,而不是把它的内容分发。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <child-component inline-template>
        <div>
            <h2>在父组件中定义子组件的模板</h2>
            <p>{{ message }}</p>
            <p>{{ msg }}</p>
        </div>
    </child-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    Vue.component('child-component',{
        data:function () {
            return {
                msg:'在子组件声明的数据'
            }
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            message:'在父组件声明的数据'
        }
    })
</script>
</body>
</html>

注:在子组件和父组件中声明的数据如果同名,优先使用子组件的数据。

7.5.3 动态组件

Vue.js提供了<component>用来动态的挂载不同的组件,使用is特性来选择要挂载的组件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <component :is="currentView">
        <button @click="handleChangeView('A')">切换到A</button>
        <button @click="handleChangeView('B')">切换到A</button>
        <button @click="handleChangeView('C')">切换到A</button>
    </component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        components:{
          comA:{
              template:'<div>组件A</div>'
          },
          comB:{
              template:'<div>组件B</div>'
          },
          comC:{
             template:'<div>组件C</div>'
          }
        },
        data:{
            currentView:'comA'
        },
        methods:{
            handleChangeView:function (component) {
                this.currentView = 'com' + component;
            }
        }
    })
</script>
</body>
</html>

7.5.4 异步组件

Vue.js允许将组件定义为一个工厂函数,动态的解析组件。Vue.js只在组件需要渲染时触发工厂函数,并且把结果缓存起来,用于后面的再次渲染。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <child-component >
    </child-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    Vue .component('child-component',function (resolve, reject) {
        window.setTimeout(function () {
            resolve({
                template:'<div>我是异步渲染</div>'
            });
        },2000);
    });
    var app = new Vue({
        el:'#app',
    })
</script>
</body>
</html>

7.6 其他

7.6.1 $nextTick

Vue在观察到数据变化时并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。在缓冲时会去除重复数据,从而避免不必要的计算和DOM操作。然后在下一个事件循环tick中,Vue刷新队列并执行十几(已去重的)工作。所以如果用一个for循环来动态改变数据100遍,其实它只会应用最后一次改变。

$nextTick是用来知道什么时候DOM更新完成的。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <div id="div" v-id="showDiv">这是一段文本</div>
    <button @click="getText">获取div内容</button>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var app = new Vue({
        el:'#app',
        data:{
            showDiv:false
        },
        methods:{
            getText:function () {
                this.showDiv = true;
                this.$nextTick(function () {
                    var text = document.getElementById('div').innerHTML;
                    console.log(text);
                })
            }
        }
    })
</script>
</body>
</html>

7.6.2 X-Templates

Vue另一种定义模板的方式:在<script>标签里使用text/template类型,并且指定一个id,将这个id赋给template。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <my-component>
        <script type="text/x-template" id="my-component">
            <div>这是组件内容</div>
        </script>
    </my-component>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    Vue.component('my-component',{
        template:'#my-component'
    })
    var app = new Vue({
        el:'#app',
    })
</script>
</body>
</html>

7.6.3 手动挂载实例

Vue提供了Vue.extend和$mount两个方法来手动挂载一个实例。

Vue.extend是基础Vue构造器,创建一个“子类”,参数是一个包含组件选项的对象。

如果Vue实例在实例化时没有收到el选项,它就出于“未挂载”状态,没有关联的DOM元素。可以使用$mount()手动的挂载一个未挂载的实例。这个方法返回实例自身, 因而可以链式调用其他实例方法。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    var MyComponent = Vue.extend({
        template:'<div>Hello:{{ name }}</div>',
        data:function () {
            return {
                name:'xu'
            }
        }
    });
    new MyComponent().$mount('#app');
</script>
</body>
</html>

其他两种写法:

new MyComponent({
    el:'#app'
});

//在文档之外渲染并且随后挂载

var component = new MyComponent().$mount();
document.getElementById('app').appendChild(component.$el);

7.7 开发一个数字输入框组件

7.7.1 需求

数字输入框只能输入数字,而且有两个快捷按钮,可以直接加1或减1,还可以设置初始值、最大值、最小值,在数值改变时,触发一个自定义事件来通知父组件。

7.7.2 实现代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>数字输入框组件</title>
</head>
<body>
<div id="app">
    <!--默认值5,最大值10,最小值0-->
    <input-number v-model="value" :max="10" :min="0"></input-number>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>

    function isValueNumber(value){
        return (/(^-?[0-9]+\.{1}\d+$)|(^-?[1-9][0-9]*$)|(^-?0{1}$)/).test(value + '');
    }

    Vue.component('input-number',{
        template:'\
        <div class="input-number">\
        <input \
        type="text"\
         :value="currentValue" \
         @change="handleChange">\
        <button \
        @click="handleDown" \
        :disabled="currentValue <= min">-</button>\
        <button \
        @click="handleUp" \
        :disabled="currentValue >= max">+</button>\
        </div>',
        props:{
            max:{
                //默认最大值:正无穷大
                type:Number,
                default:Infinity
            },
            min:{
                //默认最小值:负无穷大
                type:Number,
                default:Infinity
            },
            value:{
                //默认值为0
                type:Number,
                default:0
            }
        },
        data:function () {
            return {
                currentValue:this.value
            }
        },
        //监听,当prop或data发生改变时,就会触发watch配置的函数
        watch:{
            //监听currentValue是为了当currentValue改变时,更新value
            currentValue:function (val) {
                //在使用v-model时改变value
                this.$emit('input',val);
                //触发自定义事件on-change,用于告知父组件数字输入框的值改变了
                this.$emit('on-change',val);
            },
            //监听Value是为了知晓从父组件修改了Value
            value:function (val) {
                this.updateValue(val);
            }
        },
        methods:{
            handleDown:function () {
                if(this.currentValue <= this.min) return;
                this.currentValue -= 1;
            },
            handleUp:function () {
                if(this.currentValue >= this.max) return;
                this.currentValue += 1;
            },
            //用来过滤出一个正确的Value
            updateValue:function (val) {
                if (val > this.max) val = this.max;
                if (val < this.min ) val = this.min;
                this.currentValue = val;
            },
            handleChange:function (event) {
                var val = event.target.value.trim();
                var max = this.max;
                var min = this.min;
                if (isValueNnumber(val)) {
                    val = Number(val);
                    this.currentValue = val;
                    if (val > max) {
                        this.currentValue = max;
                    } else if (val < min) {
                        this.currentValue = min;
                    }
                } else {
                    event.target.value = this.currentValue;
                }
            }
        },
        //初始化时对value进行过滤
        mounted:function () {
            this.updateValue(this.value);
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            //给定默认值
            value:5
        }
    });
</script>
</body>
</html>

第八章 自定义指令

8.1 基本用法

自定义指令分全局注册和局部注册。

//全局注册
Vue.directive('focus',{
    //指令选项
});
//局部注册
var app = new Vue({
    el:'#app',
    directives:{
        focus:{
            //指令选项
        }
    }
})

自定义指令的选项由几个钩子函数组成的,每个都是可选的:

  1. bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
  2. inserted:被绑定元素插入父节点时调用。
  3. update:被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
  4. componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
  5. unbind:只调用一次,指令与元素解绑时调用。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <input type="text" v-focus>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    
    Vue.directive('focus',{
        inserted:function () {
            //聚焦元素
            el.focus();
        }
    });
    
    var app = new Vue({
        el:'#app',
    })
</script>
</body>
</html>

每个钩子可用的参数:

(1)el:指令所绑定的元素,可以直接用来操作DOM。

(2)binding:一个对象,包含以下属性:

①name:指令名,不包含 v-前缀。

②value:指令的绑定值。

③oldValue:指令绑定的前一个值,仅在update和componentUpdated中可用。

④expression:绑定值的字符串形式。

⑤arg:传给指令的参数。

⑥modifiers:一个包含修饰符的对象。

(3)vnode:Vue编译生成的虚拟节点。

(4)oldVnode:上一个虚拟节点积攒update和componentUpdated钩子中可用。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app">
    <div v-test:msg.a.b="message"></div>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    
    Vue.directive('test',{
        bind:function (el, binding, vnode) {
            var keys = [];
            for (var i in vnode){
                keys.push(i);
            }
            el.innerHTML =
                'name:' + binding.name + '<br>' +
                'value:' + binding.value + '<br>' +
                'expression:' + binding.expression + '<br>' +
                'argument:' + binding.argument + '<br>' +
                'modifiers:' + JSON.stringify(binding.modifiers) + '<br>' +
                'vnode keys:' + keys.join(', ') + '<br>'
        }
    });
  
    var app = new Vue({
        el:'#app',
        data:{
            message:'some text'
        }
    })
</script>
</body>
</html>

8.2 开发一个可从外部关闭的下拉菜单

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuetest</title>
</head>
<body>
<div id="app" v-cloak>
    <div class="main" v-clickoutside="handleClose">
        <button @click="show =! show">点击显示下拉菜单</button>
        <div class="dropdown" v-show="show">
            <p>下拉框的内容,点击外面区域可以关闭</p>
        </div>
    </div>
</div>

<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script>
    Vue.directive('clickoutside',{
        bind:function (el, binding, vnode) {
            function documentHandler(e) {
                if(el.contains(e.target)){
                    return false;
                }
                if(binding.expression){
                    binding.value(e);
                }
            }
            el._vueClickOutSide_ = documentHandler;
            document.addEventListener('click',documentHandler);
        },
        unbind:function (el, binding) {
            document.removeEventListener('click',el._vueClickOutSide_);
            delete el._vueClickOutSide_;
        }
    });
    var app = new Vue({
        el:'#app',
        data:{
            show:false
        },
        methods:{
            handleClose:function () {
                this.show = false;
            }
        }
    });
</script>
</body>
</html>

猜你喜欢

转载自blog.csdn.net/XuDanT/article/details/82343977