Vue学习笔记

本次Vue学习基于尚硅谷天禹老师的视频课程。

官方文档:https://cn.vuejs.org/v2/guide/

1、初识Vue

Vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

Vue的特点:

1、采用组件化模式,提高代码的复用率,让代码更好维护;

2、声明式编码,让编码人员无需直接操作DOM,提高开发效率;

3、使用虚拟DOM和优秀的Diff算法,尽量复用DOM节点;

 

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>初识Vue</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="js/vue.js"></script>
 8         <link rel="shortcut icon" type="image/x-icon" href="favicon.ico"/>
 9     </head>
10     <body>
11         <!-- 
12             前置:
13                 1、强制刷新shift+F5
14                 2、解决网页标签favicon.ico缺失的问题,内部服务器
15                 3、new Vue({这里是配置对象,要用专有名词,如el,methods})
16             初识Vue:
17                 1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;
18                 2.app容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;
19                 3.app容器里的代码被称为【Vue模板】,模板会被vue实例解析成正常的html然后挂载到容器里;
20                 4.Vue实例和容器是一一对应的,一对一的关系;
21                 5.真实开发中只有一个Vue实例,并且会配合着组件一起使用;
22                 6.{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性;
23                 7.一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新;
24 
25                 注意区分:js表达式 和 js代码(语句)
26                         1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:
27                                     (1). a
28                                     (2). a+b
29                                     (3). demo(1)
30                                     (4). x === y ? 'a' : 'b'
31                         2.js代码(语句)
32                                     (1). if(){}
33                                     (2). for(){}
34         -->
35 
36         <!-- 准备好一个容器 -->
37         <div id="app">
38             <h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
39         </div>
40 
41         <script type="text/javascript" >
42             Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
43 
44             //创建Vue实例
45             //new不可少,Vue本身是个构造函数,不能直接调用,必须通过实例来调用 
46             new Vue({  //通过构造函数创建实例
47                 el:'#app', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
48                 data:{ //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
49                     name:'waylon',
50                     address:'上海'
51                 }
52             })
53 
54         </script>
55     </body>
56 </html>

 

 

2、模板语法

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>模板语法</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 
11                 Vue模板语法有2大类:
12                     1.插值语法:
13                             功能:用于解析 标签体 内容。
14                             写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。
15                     2.指令语法:
16                             功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....)。
17                             举例:v-bind:href="xxx" 或  简写为 :href="xxx",xxx同样要写js表达式,
18                                      且可以直接读取到data中的所有属性。
19                             备注:Vue中有很多的指令,且形式都是:v-????,此处我们只是拿v-bind举个例子。
20 
21          -->
22         <!-- 准备好一个容器-->
23         <div id="root">
24             <h1>插值语法</h1>
25             <h3>你好,{{name}}</h3>
26             <hr/>
27             <h1>指令语法</h1>
28             <!-- 添加v-bind:之后就会把=右边的字符串当成js表达式去执行 -->
29             <a v-bind:href="school.url.toUpperCase()" x="hello">点我去{{school.name}}学习1</a>
30             <a :href="school.url" :x="hello">点我去{{school.name}}学习2</a>
31         </div>
32     </body>
33 
34     <script type="text/javascript">
35         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
36 
37         new Vue({
38             el:'#root',
39             data:{
40                 hello:'嘻嘻',
41                 name:'jack',
42                 school:{
43                     name:'尚硅谷', //虽然同名,但不同级
44                     url:'http://www.atguigu.com',
45                 }
46             }
47         })
48     </script>
49 </html>

 

3、数据绑定

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>数据绑定</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 
11             Vue中有2种数据绑定的方式:
12                 1.单向绑定(v-bind):数据只能从data流向页面。
13                 2.双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
14             备注:
15                 1.双向绑定一般都应用在表单类元素上(如:input、select等)
16                 2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。
17          -->
18         <!-- 准备好一个容器-->
19         <div id="root">
20             <!-- 普通写法: -->
21             <!-- 
22             单向数据绑定:<input type="text" v-bind:value="name"><br/>
23             双向数据绑定:<input type="text" v-model:value="name"><br/> 
24             -->
25 
26             <!-- 简写: -->
27             单向数据绑定:<input type="text" :value="name"><br/>
28             双向数据绑定:<input type="text" v-model="name"><br/>
29 
30             <!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->
31             <!-- <h2 v-model:x="name">你好啊</h2> -->
32         </div>
33     </body>
34 
35     <script type="text/javascript">
36         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
37 
38         new Vue({
39             el:'#root',
40             data:{
41                 name:'尚硅谷'
42             }
43         })
44     </script>
45 </html>

 

 

4、el与data的两种写法

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>el与data的两种写法</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 
11             data与el的2种写法
12                 1.el有2种写法
13                     (1).new Vue时候配置el属性。
14                     (2).先创建Vue实例,随后再通过vm.$mount('#root')指定el的值。
15                     如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可
16                     以使用 vm.$mount() 手动地挂载一个未挂载的实例。
17                 2.data有2种写法
18                     (1).对象式
19                     (2).函数式
20                     如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
21                 3.一个重要的原则:
22                     由Vue管理的函数,如data(),一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了,而是Window
23         -->
24         <!-- 准备好一个容器-->
25         <div id="root">
26             <h1>你好,{{name}}</h1>
27         </div>
28     </body>
29 
30     <script type="text/javascript">
31         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
32 
33         //el的两种写法
34         /*
35         const vm = new Vue({
36             // el:'#root', //第一种写法
37             data:{
38                 name:'尚硅谷'
39             }
40         })
41         console.log(vm)
42         vm.$mount('#root') //第二种写法,mount意为:挂载
43         */
44        
45         // data的两种写法
46         const vm = new Vue({
47             el:'#root',
48             //data的第一种写法:对象式
49             /* data:{
50                 name:'尚硅谷'
51             } */
52 
53             //data的第二种写法:函数式
54             data(){        //即data:function(){}的简写
55                 console.log('@@@',this) //此处的this是Vue实例对象
56                 return{
57                     name:'尚硅谷'
58                 }
59             }
60         })
61     </script>
62 </html>

 

5、Vue中的MVVM模型

 

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>理解MVVM</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 
11             MVVM模型
12                 1. M:模型(Model) :data中的数据   ————>js对象
13                 2. V:视图(View) :模板代码  ————>DOM
14                 3. VM:视图模型(ViewModel):Vue实例
15             观察发现:
16                 1.data中所有的属性,最后都出现在了vm身上。
17                 2.vm身上所有的属性 及 Vue原型[Prototype]上所有属性,在Vue模板中都可以直接使用。
18         -->
19         <div id="root">
20             <h1>学校名称:{{name}}</h1>
21             <h1>学校地址:{{address}}</h1>
22             <!-- <h1>测试一下1:{{1+1}}</h1>
23             <h1>测试一下2:{{$options}}</h1>
24             <h1>测试一下3:{{$emit}}</h1>
25             <h1>测试一下4:{{_c}}</h1> -->
26         </div>
27     </body>
28 
29     <script type="text/javascript">
30 
31         const vm = new Vue({
32             el:'#root',
33             data:{
34                 name:'尚硅谷',
35                 address:'北京',
36             }
37         })
38         console.log(vm)
39     </script>
40 </html>

 

Vue的实例对象vm:

 

MVVM在vue中的体现:

 

 

 

6、数据代理

1、关于Object.defineproperty方法

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="utf-8">
 5         <title>关于Object.defineproperty方法</title>
 6     </head>
 7     <body>
 8         <script type="text/javascript">
 9             let number = 18
10             let person = {
11                 name:'张三',
12                 sex:'',
13             }
14             
15             Object.defineProperty(person,'age',{
16                 // value:18,
17                 // enumerable:true, //控制属性是否可以枚举,默认值是false
18                 // writable:true, //控制属性是否可以被修改,默认值是false
19                 // configurable:true //控制属性是否可以被删除,默认值是false
20             
21                 //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
22                 get(){
23                     console.log('有人读取age属性了')
24                     return number
25                 },
26             
27                 //当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
28                 set(value){
29                     console.log('有人修改了age属性,且值是',value)
30                     number = value
31                 }
32                 //getter和setter将person和number两者产生了关联
33             })
34             // console.log(Object.keys(person)) //将对象的属性名提取出来组成一个数组
35             
36             console.log(person)
37             52         </script>
53     </body>
54 </html>

2、何为数据代理?

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>何为数据代理</title>
 6     </head>
 7     <body>
 8         
 9         <!-- 数据代理:通过一个对象实现对另一个对象中属性的(读/写)操作 -->
10         <script type="text/javascript" >
11             let obj = {x:100}
12             let obj2 = {y:200}
13 
14             Object.defineProperty(obj2,'x',{
15                 get(){
16                     return obj.x
17                 },
18                 set(value){
19                     obj.x = value
20                 }
21             })
22         </script>
23     </body>
24 </html>

 

3、在Vue中的数据代理如何实现?

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="utf-8">
 5         <title>Vue中的数据代理</title>
 6         <script type="text/javascript" src="js/vue.js"></script>
 7     </head>
 8     <body>
 9         <div id="app">
10             <h1>个人姓名:{{name}}</h1>
11             <h1>个人年龄:{{age}}</h1>
12         </div>
13         <script type="text/javascript">
14             const vm = new Vue({
15                 el:"#app",
16                 data:{
17                     name:'天青色',
18                     age:18
19                 }
20             })
21         </script>
22     </body>
23 </html>

 

 

在上面这段代码中,data属性是个对象,可以通过实例对象vm去操作data中的键值对,如下:

 

 

 

 

 

 这不就是数据代理吗?通过一个对象实现对另一个对象中属性的(读/写)操作 。这点可以从vm实例对象下面的name属性看出来,将鼠标光标移到上面显示 Invoke property getter,表示读取属性值时要通过getter实现。

 

name和age都是有自己的getter和setter:

 

 

其实在vm实例对象下面有个_data属性,里面保存了data里的键值对,可以通过vm._data来进行读写操作:

 

 

 

 

 vm下面的name和age就是通过数据代理与_data中的name和age建立了联系,如果没有这层联系,那模板中{{name}}就不能直接拿到_data中的name了,而应该是{{ _data.name }}才可以。而_data数据又来自于data,所以vm就很方便的代理操作了data。

尚硅谷老师的图也讲得很清楚:

 

 

 补充一点,_data里还实现了数据劫持。当我们通过vm.name更改值时,页面上用到name的地方也会自动响应式更新了。这就是_data里数据劫持的作用。

 

总结:

1.Vue中的数据代理:
  通过vm对象来代理data对象中属性的操作(读/写)
2.Vue中数据代理的好处:
  更加方便的操作data中的数据
3.基本原理:
  通过Object.defineProperty()把data对象中所有属性添加到vm上。
  为每一个添加到vm上的属性,都指定一个getter/setter。
  在getter/setter内部去操作(读/写)data中对应的属性。

 

7、事件处理

 

 

8、计算属性

先实现一个小案例:

 

 

 

 输入框默认有张和三,手动输入后全名也会跟着改变。

1、通过插值语法实现案例

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>姓名案例_插值语法实现</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 准备好一个容器-->
11         <div id="root">
12             姓:<input type="text" v-model="firstName"> <br/><br/>
13             名:<input type="text" v-model="lastName"> <br/><br/>
14             全名:<span>{{firstName}}-{{lastName}}</span>
15         </div>
16     </body>
17 
18     <script type="text/javascript">
19         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
20 
21         new Vue({
22             el:'#root',
23             data:{
24                 firstName:'',
25                 lastName:''
26             }
27         })
28     </script>
29 </html>

 

2、通过methods实现案例

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>姓名案例_methods实现</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 准备好一个容器-->
11         <div id="root">
12             姓:<input type="text" v-model="firstName"> <br/><br/>
13             名:<input type="text" v-model="lastName"> <br/><br/>
14             全名:<span>{{fullName()}}</span>
15         </div>
16     </body>
17 
18     <script type="text/javascript">
19         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
20 
21         new Vue({
22             el:'#root',
23             data:{
24                 firstName:'',
25                 lastName:''
26             },
27             methods: {
28                 fullName(){
29                     console.log('@---fullName')
30                     return this.firstName + '-' + this.lastName
31                 }
32             },
33         })
34     </script>
35 </html>

注意14行的{{fullName()}}是要加()的。

另外,由于是双向数据绑定,每次输入都会导致data对象里面的属性值发生改变,从而会使得模板代码被Vue重新渲染,fullName方法也会被重新调用。如下:

 

3、通过计算属性实现案例

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>姓名案例_计算属性实现</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="js/vue.js"></script>
 8     </head>
 9     <body>
21         <!-- 准备好一个容器-->
22         <div id="root">
23             姓:<input type="text" v-model="firstName"> <br/><br/>
24             名:<input type="text" v-model="lastName"> <br/><br/>
25             测试:<input type="text" v-model="x"> <br/><br/>
26             全名:<span>{{fullName}}</span> <br/><br/>
27             <!-- 全名:<span>{{fullName}}</span> <br/><br/>
28             全名:<span>{{fullName}}</span> <br/><br/>
29             全名:<span>{{fullName}}</span> -->
30         </div>
31     </body>
32 
33     <script type="text/javascript">
34         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
35 
36         const vm = new Vue({
37             el:'#root',
38             data:{
39                 firstName:'',
40                 lastName:'',
41                 x:'你好'
42             },
43             methods: {
44                 demo(){
45                     
46                 }
47             },
48             computed:{
49                 fullName:{
50                     //get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
51                     //get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
52                     get(){
53                         console.log('get被调用了')
54                         // console.log(this) //经过Vue的处理后此处的this是vm
55                         return this.firstName + '-' + this.lastName
56                     },
57                     //set什么时候调用? 当fullName被修改时。
58                     set(value){
59                         console.log('set',value)
60                         const arr = value.split('-')
61                         this.firstName = arr[0]
62                         this.lastName = arr[1]
63                     }
64                 }
65             }
66         })
67     </script>
68 </html>

 

 

总结:

1.定义:要用的属性不存在,要通过已有属性(如data对象里的属性)计算得来。它也是vm下面的一个属性。
2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
3.get函数什么时候执行?
  (1).初次读取时会执行一次。
  (2).当依赖的数据发生改变时会被再次调用。
4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
5.计算属性最终会出现在vm上,直接插值法读取使用即可。
6.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

 

4、计算属性的简写

当只考虑计算属性的读取不考虑修改的时候,就可以简写:

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>姓名案例_计算属性实现</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 准备好一个容器-->
11         <div id="root">
12             姓:<input type="text" v-model="firstName"> <br/><br/>
13             名:<input type="text" v-model="lastName"> <br/><br/>
14             全名:<span>{{fullName}}</span> <br/><br/>
15         </div>
16     </body>
17 
18     <script type="text/javascript">
19         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
20 
21         const vm = new Vue({
22             el:'#root',
23             data:{
24                 firstName:'',
25                 lastName:'',
26             },
27             computed:{
28                 //完整写法
29                 /* fullName:{
30                     get(){
31                         console.log('get被调用了')
32                         return this.firstName + '-' + this.lastName
33                     },
34                     set(value){
35                         console.log('set',value)
36                         const arr = value.split('-')
37                         this.firstName = arr[0]
38                         this.lastName = arr[1]
39                     }
40                 } */
41                 //简写
42                 fullName(){
43                     console.log('get被调用了')
44                     return this.firstName + '-' + this.lastName
45                 }
46             }
47         })
48     </script>
49 </html>

 

 读取的时候依然是 {{计算属性}} ,不可加() 。

 

9、监视属性

实现天气小案例:

 

 默认炎热,点击后切换为凉爽,再点击切换为炎热。

1、实现案例

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="utf-8">
 5         <title></title>
 6         <script type="text/javascript" src="js/vue.js"></script>
 7     </head>
 8     <body>
 9         <div id="app">
10             <h1>今天的天气很{{weather}}</h1>
11             <button type="button" @click="changeWeather">点我切换天气</button>
12             <!-- <button type="button" @click="ishot=!ishot;x其他代码x">点我切换天气</button> -->
13             <!-- <button type="button" @click="window.alert('点击成功')">点我切换天气</button> -->
14             <!-- 在绑定事件的时候,@xxx='yyy',这里的yyy可以写一些简单的语句,复杂的话就交给methods -->
15         </div>
16         <script type="text/javascript">
17             const vm = new Vue({
18                 el:'#app',
19                 data:{
20                     ishot:true,
21                     // window  点击事件里的window在vm里找不到,所以要在这添加window,不然会报错
22                 },
23                 methods:{
24                     changeWeather(){
25                         this.ishot = !this.ishot
26                     }
27                 },
28                 computed:{
29                     weather(){
30                         return this.ishot ? '炎热' : '凉爽'
31                     }
32                 }
33             })
34         </script>
35     </body>
36 </html>

 

 

 

2、监视属性watch

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>天气案例_监视属性</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 准备好一个容器-->
11         <div id="root">
12             <h2>今天天气很{{info}}</h2>
13             <button @click="changeWeather">切换天气</button>
14         </div>
15     </body>
16 
17     <script type="text/javascript">
18         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
19         
20         const vm = new Vue({
21             el:'#root',
22             data:{
23                 isHot:true,
24             },
25             computed:{
26                 info(){
27                     return this.isHot ? '炎热' : '凉爽'
28                 }
29             },
30             methods: {
31                 changeWeather(){
32                     this.isHot = !this.isHot
33                 }
34             },
35             /* watch:{
36                 isHot:{
37                     immediate:true, //初始化时让handler调用一下
38                     //handler什么时候调用?当isHot发生改变时。
39                     handler(newValue,oldValue){
40                         console.log('isHot被修改了',newValue,oldValue)
41                     }
42                 }
43             } */
44         })
45 
46         vm.$watch('isHot',{
47             immediate:true, //初始化时让handler调用一下
48             //handler什么时候调用?当isHot发生改变时。
49             handler(newValue,oldValue){
50                 console.log('isHot被修改了',newValue,oldValue)
51             }
52         })
53     </script>
54 </html>

 

总结:

监视属性watch:
1.当被监视的属性变化时, 回调函数自动调用, 进行相关操作
2.监视的属性必须存在,才能进行监视!可以使data里的属性,也可以是计算属性
3.监视的两种写法:
  (1).new Vue时传入watch配置(初期明确知道需要监视)
  (2).通过vm.$watch监视(后期需求添加的监视)

 

3、深度监视deep

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>天气案例_深度监视</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 准备好一个容器-->
11         <div id="root">
12             <h2>今天天气很{{info}}</h2>
13             <button @click="changeWeather">切换天气</button>
14             <hr/>
15             <h3>a的值是:{{numbers.a}}</h3>
16             <button @click="numbers.a++">点我让a+1</button>
17             <h3>b的值是:{{numbers.b}}</h3>
18             <button @click="numbers.b++">点我让b+1</button>
19             <button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
20             <!-- {{numbers.c.d.e}} -->
21         </div>
22     </body>
23 
24     <script type="text/javascript">
25         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
26         
27         const vm = new Vue({
28             el:'#root',
29             data:{
30                 isHot:true,
31                 numbers:{
32                     a:1,
33                     b:1,
34                     c:{
35                         d:{
36                             e:100
37                         }
38                     }
39                 }
40             },
41             computed:{
42                 info(){
43                     return this.isHot ? '炎热' : '凉爽'
44                 }
45             },
46             methods: {
47                 changeWeather(){
48                     this.isHot = !this.isHot
49                 }
50             },
51             watch:{
52                 isHot:{
53                     // immediate:true, //初始化时让handler调用一下
54                     //handler什么时候调用?当isHot发生改变时。
55                     handler(newValue,oldValue){
56                         console.log('isHot被修改了',newValue,oldValue)
57                     }
58                 },
59                 //监视多级结构中某个属性的变化,注意要用引号,之前isHot是简写
60                 /* 'numbers.a':{
61                     handler(){
62                         console.log('a被改变了')
63                     }
64                 } */
65                 //监视多级结构中所有属性的变化
66                 numbers:{
67                     deep:true,  //默认为false,开启深度监视
68                     handler(){
69                         console.log('numbers改变了')
70                     }
71                 }
72             }
73         })
74 
75     </script>
76 </html>

 

 

总结:

深度监视:
(1).Vue中的watch默认不监测对象内部值的改变(一层)。
(2).配置deep:true可以监测对象内部值改变(多层)。
(3).Vue自身可以监测对象内部值的改变,无论有多少层,如numbers.c.d.e,但Vue提供的watch默认不可以!
(4).考虑到效率性,使用watch时根据数据的具体结构,决定是否采用深度监视。

 

4、监视属性简写

当监视某个属性而不需要配置immediate和deep时,可以使用简写形式,类似于计算属性的简写:

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>天气案例_监视属性_简写</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 准备好一个容器-->
11         <div id="root">
12             <h2>今天天气很{{info}}</h2>
13             <button @click="changeWeather">切换天气</button>
14         </div>
15     </body>
16 
17     <script type="text/javascript">
18         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
19         
20         const vm = new Vue({
21             el:'#root',
22             data:{
23                 isHot:true,
24             },
25             computed:{
26                 info(){
27                     return this.isHot ? '炎热' : '凉爽'
28                 }
29             },
30             methods: {
31                 changeWeather(){
32                     this.isHot = !this.isHot
33                 }
34             },
35             watch:{
36                 //正常写法
37                 /* isHot:{
38                     // immediate:true, //初始化时让handler调用一下
39                     // deep:true,//深度监视
40                     handler(newValue,oldValue){
41                         console.log('isHot被修改了',newValue,oldValue)
42                     }
43                 }, */
44                 //简写
45                 /* isHot(newValue,oldValue){
46                     console.log('isHot被修改了',newValue,oldValue,this)
47                 } */
48             }
49         })
50 
51         //正常写法
52         /* vm.$watch('isHot',{
53             immediate:true, //初始化时让handler调用一下
54             deep:true,//深度监视
55             handler(newValue,oldValue){
56                 console.log('isHot被修改了',newValue,oldValue)
57             }
58         }) */
59 
60         //简写
61         /* vm.$watch('isHot',(newValue,oldValue)=>{
62             console.log('isHot被修改了',newValue,oldValue,this)
63         }) */
64 
65     </script>
66 </html>

 

5、watch实现天气案例

 

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>姓名案例_watch实现</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="../js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 准备好一个容器-->
11         <div id="root">
12             姓:<input type="text" v-model="firstName"> <br/><br/>
13             名:<input type="text" v-model="lastName"> <br/><br/>
14             全名:<span>{{fullName}}</span> <br/><br/>
15         </div>
16     </body>
17 
18     <script type="text/javascript">
19         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
20 
21         const vm = new Vue({
22             el:'#root',
23             data:{
24                 firstName:'',
25                 lastName:'',
26                 fullName:'张-三'
27             },
28             watch:{
29                 firstName(val){
30                     setTimeout(()=>{ //此处如果写普通函数则this为window
31                         console.log(this)
32                         this.fullName = val + '-' + this.lastName
33                     },1000);
34                 },
35                 lastName(val){
36                     this.fullName = this.firstName + '-' + val
37                 }
38             }
39         })
40     </script>
41 </html>

 

总结:

computed和watch之间的区别:
1.computed能完成的功能,watch都可以完成。谁简单用谁。
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作,比如延迟,而计算属性却不能延迟得到返回值
两个重要的小原则:
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,自身无this,向外找,这样this的指向才是vm 或 组件实例对象。

 

 

 

 

12、列表渲染

1、基本列表

使用v-for指令来渲染列表:

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>基本列表</title>
 6         <script type="text/javascript" src="../js/vue.js"></script>
 7     </head>
 8     <body>
 9         <!-- 
10                 v-for指令:
11                         1.用于展示列表数据
12                         2.语法:v-for="(item, index) in xxx" :key="yyy"
13                         3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
14         -->
15         <!-- 准备好一个容器-->
16         <div id="root">
17             <!-- 遍历数组 -->
18             <h2>人员列表(遍历数组)</h2>
19             <ul>
20                 <li v-for="(p,index) of persons" :key="index">
21                     {{p.name}}-{{p.age}}
22                 </li>
23             </ul>
24 
25             <!-- 遍历对象 -->
26             <h2>汽车信息(遍历对象)</h2>
27             <ul>
28                 <li v-for="(value,k) of car" :key="k">
29                     {{k}}-{{value}}
30                 </li>
31             </ul>
32             <h2>汽车信息(遍历对象)</h2>
33             <ul>
34                 <li v-for="a of car" >
35                     只有值,无key:{{a}}
36                 </li>
37             </ul>
38 
39             <!-- 遍历字符串 -->
40             <h2>测试遍历字符串(用得少)</h2>
41             <ul>
42                 <li v-for="(char,index) of str" :key="index">
43                     {{char}}-{{index}}
44                 </li>
45             </ul>
46             
47             <!-- 遍历指定次数 -->
48             <h2>测试遍历指定次数(用得少)</h2>
49             <ul>
50                 <li v-for="(number,index) of 5" :key="index">
51                     {{index}}-{{number}}
52                 </li>
53             </ul>
54         </div>
55 
56         <script type="text/javascript">
57             Vue.config.productionTip = false
58             
59             new Vue({
60                 el:'#root',
61                 data:{
62                     persons:[
63                         {id:'001',name:'张三',age:18},
64                         {id:'002',name:'李四',age:19},
65                         {id:'003',name:'王五',age:20}
66                     ],
67                     car:{
68                         name:'奥迪A8',
69                         price:'70万',
70                         color:'黑色'
71                     },
72                     str:'hello'
73                 }
74             })
75         </script>
76 </html>

 

 

 

2、key的原理

 

3、列表过滤

 

4、列表排序

 

5、更新数据出现的问题

更新数据时出现了一个问题。

需求:点击修改按钮,就能修改第一行的马冬梅的信息。

第一种方式:通过修改对象属性的方式去修改,vue能成功监测到数据修改,并且更新到页面显示。

第二种方式:通过修改整个对象的方式去修改,当点击按钮后,页面并未更新显示最新的马冬梅的信息,而通过控制台确实发现马冬梅的信息已经被修改,打开vue开发者工具,发现vue也监测到数据的改变:

 

  

 

 但如果先打开vue开发者工具,再去点击更新按钮,会发现vue甚至都没监测到数据的改变,当然页面也不会更新显示最新的马冬梅信息。

 

 

 

 这个问题涉及到vue监测数据的原理。

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>更新时的一个问题</title>
 6         <script type="text/javascript" src="../js/vue.js"></script>
 7     </head>
 8     <body>
 9         <!-- 准备好一个容器-->
10         <div id="root">
11             <h2>人员列表</h2>
12             <button @click="updateMei">更新马冬梅的信息</button>
13             <ul>
14                 <li v-for="(p,index) of persons" :key="p.id">
15                     {{p.name}}-{{p.age}}-{{p.sex}}
16                 </li>
17             </ul>
18         </div>
19 
20         <script type="text/javascript">
21             Vue.config.productionTip = false
22             
23             const vm = new Vue({
24                 el:'#root',
25                 data:{
26                     persons:[
27                         {id:'001',name:'马冬梅',age:30,sex:''},
28                         {id:'002',name:'周冬雨',age:31,sex:''},
29                         {id:'003',name:'周杰伦',age:18,sex:''},
30                         {id:'004',name:'温兆伦',age:19,sex:''}
31                     ]
32                 },
33                 methods: {
34                     updateMei(){
35                         // this.persons[0].name = '马老师' //奏效
36                         // this.persons[0].age = 50 //奏效
37                         // this.persons[0].sex = '男' //奏效
38                         this.persons[0] = {id:'001',name:'马老师',age:50,sex:''} //不奏效
39                         
40                     }
41                 }
42             }) 
43 
44         </script>
45 </html>

 

6、Vue监测数据的原理

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>数据监视原理总结</title>
 6         <style>
 7             button{
 8                 margin-top: 10px;
 9             }
10         </style>
11         <!-- 引入Vue -->
12         <script type="text/javascript" src="../js/vue.js"></script>
13     </head>
14     <body>
15         <!-- 准备好一个容器-->
16         <div id="root">
17             <h1>学生信息</h1>
18             <button @click="student.age++">年龄+1岁</button> <br/>
19             <button @click="addSex">添加性别属性,默认值:男</button> <br/>
20             <button @click="student.sex = '未知' ">修改性别</button> <br/>
21             <button @click="addFriend">在列表首位添加一个朋友</button> <br/>
22             <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br/>
23             <button @click="updateSecondFriendName">修改第二个朋友的名字为:天青色,年龄为18</button> <br/>
24             <button @click="addHobby">添加一个爱好</button> <br/>
25             <button @click="updateHobby">修改第一个爱好为:开车</button> <br/>
26             <button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br/>
27             <h3>姓名:{{student.name}}</h3>
28             <h3>年龄:{{student.age}}</h3>
29             <h3 v-if="student.sex">性别:{{student.sex}}</h3>
30             <h3>爱好:</h3>
31             <ul>
32                 <li v-for="(h,index) in student.hobby" :key="index">
33                     {{h}}
34                 </li>
35             </ul>
36             <h3>朋友们:</h3>
37             <ul>
38                 <li v-for="(f,index) in student.friends" :key="index">
39                     {{f.name}}--{{f.age}}
40                 </li>
41             </ul>
42         </div>
43     </body>
44 
45     <script type="text/javascript">
46         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
47 
48         const vm = new Vue({
49             el:'#root',
50             data:{
51                 student:{
52                     name:'tom',
53                     age:18,
54                     hobby:['抽烟','喝酒','烫头'],
55                     friends:[
56                         {name:'jerry',age:35},
57                         {name:'tony',age:36}
58                     ]
59                 }
60             },
61             methods: {
62                 addSex(){
63                     // Vue.set(this.student,'sex','男')
64                     this.$set(this.student,'sex','')
65                 },
66                 addFriend(){
67                     this.student.friends.unshift({name:'jack',age:70})
68                 },
69                 
70                 // 数组里的对象不是响应式的,而数组里的对象的属性却是响应式属性,有setter/getter
71                 updateFirstFriendName(){
72                     this.student.friends[0].name = '张三' 
73                 },
74                 updateSecondFriendName(){
75                     // Vue.set(this.student.friends,1,{name:'天青色',age:18})
76                     // this.$set(this.student.friends,1,{name:'天青色',age:18})
77                     this.student.friends.splice(1,1,{name:'天青色',age:18})
78                     // this.student.friends[1] = {name:'天青色',age:18}  //vue检测不到,不会响应式视图更新
79                 },
80                 
81                 addHobby(){
82                     this.student.hobby.push('学习')
83                 },
84                 updateHobby(){
85                     // this.student.hobby.splice(0,1,'开车')
86                     // Vue.set(this.student.hobby,0,'开车')
87                     this.$set(this.student.hobby,0,'开车')
88                 },
89                 removeSmoke(){
90                     this.student.hobby = this.student.hobby.filter((h)=>{
91                         return h !== '抽烟'
92                     })
93                 }
94             }
95         })
96     </script>
97 </html>

 

 

Vue监视数据的原理:
1、 vue会监视data中所有层次的数据。递归处理,有多少层都能监测。

2、监测data中的对象类型的数据:
通过setter实现监视,且要在new Vue时就传入要监测的数据。如果当时没有具体的数据,就留空。对象中后追加的属性,Vue默认不做响应式处理。

3、如需给后添加的属性(非根级别)做响应式,可以使用如下API:
Vue.set(target,propertyName/index,value) 或

vm.$set(target,propertyName/index,value)

注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象添加属性!!!

4、 监测data中数组类型的数据:

涉及到元素更改的方法有七个:push()、pop()、shift()、unshift()、splice()、sort()、reverse()

这七个Vue在内部都对他们进行了重写,当我们调用这些方法时,首先vue会调用JS原生对应的方法对数组进行更新,然后重新解析模板,进而更新页面。这样就实现了数据监测。

这也是数组和对象监测的不同点。从vue实例里的_data中也可以看出,对象类型的数据都有对应的Setter和Getter,而数组类型的数据却没有。没有则不能直接进行监测。

 

 

Vue.set() 或 vm.$set()也能对数组数据进行修改并且vue能监视到。

这些在Vue文档的深入响应式原理这一节都有所说明:

 

 

 

13、收集表单数据

 注意,在checkbox类型的输入框中,指定任意checked的值,chrome浏览器都会将其变为checked="checked",即选中状态。

 

 

 

 通过js将checked的值变为布尔值会更改选中状态。

14、过滤器

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>过滤器</title>
 6         <script type="text/javascript" src="../js/vue.js"></script>
 7         <script type="text/javascript" src="../js/dayjs.min.js"></script>
 8     </head>
 9     <body>
10         <!-- 
11             过滤器:
12                 定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
13                 语法:
14                     1.注册过滤器:
15                         全局过滤器Vue.filter(name,callback) 
16                         局部过滤器:new Vue{filters:{}}
17                     2.使用过滤器:{{ xxx | 过滤器名}}  或  v-bind:属性 = "xxx | 过滤器名"
18                 备注:
19                     1.过滤器也可以接收额外参数、多个过滤器也可以串联
20                     2.并没有改变原本的数据, 是产生新的对应的数据
21         -->
22         <!-- 准备好一个容器-->
23         <div id="root">
24             <h2>显示格式化后的时间</h2>
25             <!-- 计算属性实现 -->
26             <h3>1现在是:{{fmtTime}}</h3>
27             <!-- methods实现 -->
28             <h3>2现在是:{{getFmtTime()}}</h3>
29             <!-- 过滤器实现 -->
30             <h3>3现在是:{{time | timeFormater}}</h3>
31             <!-- 过滤器实现(传参) -->
32             <h3>4现在是:{{time | timeFormater('YYYY_MM_DD')}}</h3>
33             <!-- 过滤器串联 -->
34             <h3>5现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
35             <h3 :x="msg | mySlice">尚硅谷</h3>
36         </div>
37 
38         <div id="root2">
39             <h2>{{msg | mySlice}}</h2>
40         </div>
41     </body>
42 
43     <script type="text/javascript">
44         Vue.config.productionTip = false
45         //全局过滤器
46         Vue.filter('mySlice',function(value){
47             return value.slice(0,4)
48         })
49         
50         new Vue({
51             el:'#root',
52             data:{
53                 time:1621561377603, //时间戳
54                 msg:'你好,尚硅谷'
55             },
56             computed: {
57                 fmtTime(){
58                     return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
59                 }
60             },
61             methods: {
62                 getFmtTime(){
63                     return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
64                 }
65             },
66             //局部过滤器
67             filters:{
68                 timeFormater(value,str='YYYY年MM月DD日 HH:mm:ss'){
69                     // console.log('@',value)
70                     return dayjs(value).format(str)
71                 }
72             }
73         })
74 
75         new Vue({
76             el:'#root2',
77             data:{
78                 msg:'hello,atguigu!'
79             }
80         })
81     </script>
82 </html>

dayjs.js库来自于BootCDN:

 

 

15、内置指令

 

16、自定义指令

 Vue内置指令满足不了需求时,可以自定义指令,包含全局的和局部的,类似于过滤器。

 

  1 <!DOCTYPE html>
  2 <html>
  3     <head>
  4         <meta charset="UTF-8" />
  5         <title>自定义指令</title>
  6         <script type="text/javascript" src="../js/vue.js"></script>
  7     </head>
  8     <body>
  9         <!-- 
 10             需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
 11             需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
 12             自定义指令总结:
 13                 一、定义语法:
 14                     (1).局部指令:
 15                                 new Vue({                                                            new Vue({
 16                                     directives:{指令名:配置对象}   或           directives{指令名:回调函数}
 17                                 })                                                                         })
 18                     (2).全局指令:
 19                                     Vue.directive(指令名,配置对象) 或   Vue.directive(指令名,回调函数)
 20 
 21                 二、配置对象中常用的3个回调:
 22                     (1).bind:指令与元素成功绑定时调用。
 23                     (2).inserted:指令所在元素被插入页面时调用。
 24                     (3).update:指令所在模板结构被重新解析时调用。
 25 
 26                 三、备注:
 27                     1.指令定义时不加v-,但使用时要加v-;
 28                     2.如果指令名由多个单词组成,定义的时候就用字符串形式“-”连接,不能写简写形式,如'big-number',引用则v-big-number
 29         -->
 30         <!-- 准备好一个容器-->
 31         <div id="root">
 32             <h2>{{name}}</h2>
 33             <h2>当前的n值是:<span v-text="n"></span> </h2>
 34             <!-- <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2> -->
 35             <h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
 36             <button @click="n++">点我n+1</button>
 37             <hr/>
 38             <input type="text" v-fbind:value="n">
 39         </div>
 40     </body>
 41     
 42     <script type="text/javascript">
 43         Vue.config.productionTip = false
 44 
 45         //定义全局指令
 46         
 47         // 全局指令对象形式,第二个参数是对象
 48         /* Vue.directive('fbind',{
 49             //指令与元素成功绑定时(一上来)
 50             bind(element,binding){
 51                 element.value = binding.value
 52             },
 53             //指令所在元素被插入页面时
 54             inserted(element,binding){
 55                 element.focus()
 56             },
 57             //指令所在的模板被重新解析时
 58             update(element,binding){
 59                 element.value = binding.value
 60             }
 61         }) */
 62         
 63         //全局指令函数形式,第二个参数是函数
 64         /* Vue.directive('big',function(element,binding){
 65             console.log(element,binding)
 66             console.log('big',this) //注意此处的this是window
 67             // console.log('big')
 68             element.innerText = binding.value * 10)} */
 69 
 70         new Vue({
 71             el:'#root',
 72             data:{
 73                 name:'尚硅谷',
 74                 n:1
 75             },
 76             directives:{
 77                 //big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
 78                 /* 'big-number'(element,binding){   //指令名太长就用字符串形式“-”连接,不能写简写形式
 79                     // console.log('big')
 80                     element.innerText = binding.value * 10
 81                 }, */
 82                 
 83                 //函数形式的
 84                 big(element,binding){
 85                     console.log(element,binding)
 86                     console.log('big',this) //注意此处的this是window
 87                     // console.log('big')
 88                     element.innerText = binding.value * 10
 89                 },
 90                 /* 对象形式的,可添加特定的回调函数(钩子函数),实现特殊的功能,而上面函数形式的
 91                 简单形式就默认只有两个钩子函数bing和update,这两个里面的功能通常是一样的*/ 
 92                 fbind:{
 93                     //指令与元素成功绑定时(一上来)
 94                     bind(element,binding){
 95                         element.value = binding.value
 96                     },
 97                     //指令所在元素被插入页面时
 98                     inserted(element,binding){
 99                         element.focus()
100                     },
101                     //指令所在的模板被重新解析时
102                     update(element,binding){
103                         element.value = binding.value
104                     }
105                 }
106             }
107         })
108         
109     </script>
110 </html>

 

 对于剩下的两个钩子函数和钩子函数的参数:

 

 

 

 

 

 

 

 

17、Vue生命周期

 1、什么是Vue生命周期

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>引出生命周期</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="../js/vue.js"></script>
 8     </head>
 9     <body>
17         <!-- 准备好一个容器-->
18         <div id="root">
19             <h2 v-if="a">你好啊</h2>
20             <h2 :style="{opacity}">欢迎学习Vue</h2>
21         </div>
22     </body>
23 
24     <script type="text/javascript">
25         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
26         
27          new Vue({
28             el:'#root',
29             data:{
30                 a:false,    //页面加载后更改a的值为true,验证初始的DOM挂载完毕才会调用一次mounted
31                 opacity:1
32             },
33             methods: {
34                 
35             },
36             //Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
37             mounted(){
38                 console.log('mounted',this)
39                 setInterval(() => {
40                     this.opacity -= 0.01
41                     if(this.opacity <= 0) this.opacity = 1
42                 },16)
43             },
44         })
45 
46         //通过外部的定时器实现(不推荐)
47         /* setInterval(() => {
48             vm.opacity -= 0.01
49             if(vm.opacity <= 0) vm.opacity = 1
50         },16) */
51     </script>
52 </html>

 

 

生命周期:
1.又名:生命周期回调函数、生命周期函数、生命周期钩子。
2.是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数。
3.生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
4.生命周期函数中的this指向是vm 或 组件实例对象。

 

2、分析生命周期

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>分析生命周期</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="../js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 准备好一个容器-->
11         <div id="root" :x="n">
12             <h2 v-text="n"></h2>
13             <h2>当前的n值是:{{n}}</h2>
14             <button @click="add">点我n+1</button>
15             <button @click="bye">点我销毁vm</button>
16         </div>
17     </body>
18 
19     <script type="text/javascript">
20         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
21 
22         new Vue({
23             el:'#root', //没有el则不会挂载,可以手动调用实例的$mount('#root')
24             // template:`    // 如果有模板,会替换掉body内的容器,innerHTML,不再有id为root的div
25             //     <div>
26             //         <h2>当前的n值是:{{n}}</h2>
27             //         <button @click="add">点我n+1</button>
28             //     </div>
29             // `,
30             data:{
31                 n:1
32             },
33             methods: {
34                 add(){
35                     console.log('add')
36                     this.n++
37                 },
38                 bye(){
39                     console.log('bye')
40                     this.$destroy()
41                 }
42             },
43             watch:{
44                 n(){
45                     console.log('n变了')
46                 }
47             },
48             beforeCreate() {
49                 console.log('beforeCreate')
50             },
51             created() {
52                 console.log('created')
53             },
54             beforeMount() {
55                 console.log('beforeMount')
56             },
57             mounted() {
58                 console.log('mounted')
59                 console.log(this.$el)
60                 console.log(this.$el instanceof HTMLElement)
61             },
62             beforeUpdate() {
63                 console.log('beforeUpdate')
64                 console.log(this.n) 
65                 debugger    //修改n后页面还没更新修改
66             },
67             updated() {
68                 console.log('updated')
69             },
70             beforeDestroy() {
71                 console.log('beforeDestroy')
72             },
73             destroyed() {
74                 console.log('destroyed')
75             },
76         })
77     </script>
78 </html>

 

 

 

 

 生命周期图:

 

 

 模板到视图的转化过程:

 

 

 更新时,通过diff算法将新旧虚拟dom进行对比,即对比新旧虚拟节点,vnode和oldVnode,找出真正需要更新的节点,在真实DOM上进行针对性修改,从而实现视图的更新。

 关于虚拟DOM可以参考这个文章:https://www.cnblogs.com/fundebug/p/vue-virtual-dom.html

 3、总结生命周期

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>引出生命周期</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="../js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 
11             常用的生命周期钩子:
12                 1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
13                 2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
14 
15             关于销毁Vue实例
16                 1.销毁后借助Vue开发者工具看不到任何信息。
17                 2.销毁后自定义事件会失效,但原生DOM事件依然有效。
18                 3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
19         -->
20         <!-- 准备好一个容器-->
21         <div id="root">
22             <h2 :style="{opacity}">欢迎学习Vue</h2>
23             <button @click="opacity = 1">透明度设置为1</button>
24             <button @click="stop">点我停止变换</button>
25         </div>
26     </body>
27 
28     <script type="text/javascript">
29         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
30 
31          new Vue({
32             el:'#root',
33             data:{
34                 opacity:1
35             },
36             methods: {
37                 stop(){
38                     //不要在这里关闭定时器
39                     this.$destroy()
40                 }
41             },
42             //Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
43             mounted(){
44                 console.log('mounted',this)
45                 this.timer = setInterval(() => {
46                     console.log('setInterval')
47                     this.opacity -= 0.01
48                     if(this.opacity <= 0) this.opacity = 1
49                 },16)
50             },
51             beforeDestroy() {
52                 clearInterval(this.timer)
53                 console.log('vm即将驾鹤西游了')
54             },
55         })
56 
57     </script>
58 </html>

 

 

 

18、非单文件组件

1、组件的基本使用

  1 <!DOCTYPE html>
  2 <html>
  3     <head>
  4         <meta charset="UTF-8" />
  5         <title>基本使用</title>
  6         <script type="text/javascript" src="../js/vue.js"></script>
  7     </head>
  8     <body>
  9         -->
 10         <!-- 准备好一个容器-->
 11         <div id="root">
 12             <hello></hello>
 13             <hr>
 14             <h1>{{msg}}</h1>
 15             <hr>
 16             <!-- 第三步:编写组件标签 -->
 17             <school></school>
 18             <hr>
 19             <!-- 第三步:编写组件标签 -->
 20             <student></student>
 21         </div>
 22 
 23         <div id="root2">
 24             <hello></hello>
 25         </div>
 26     </body>
 27 
 28     <script type="text/javascript">
 29         Vue.config.productionTip = false
 30 
 31         //第一步:创建school组件
 32         const school = Vue.extend({
 33             template:`
 34                 <div class="demo">
 35                     <h2>学校名称:{{schoolName}}</h2>
 36                     <h2>学校地址:{{address}}</h2>
 37                     <button @click="showName">点我提示学校名</button>    
 38                 </div>
 39             `,
 40             // el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
 41             data(){
 42                 return {
 43                     schoolName:'尚硅谷',
 44                     address:'北京昌平'
 45                 }
 46             },
 47             methods: {
 48                 showName(){
 49                     alert(this.schoolName)
 50                 }
 51             },
 52         })
 53 
 54         //第一步:创建student组件
 55         const student = Vue.extend({
 56             template:`
 57                 <div>
 58                     <h2>学生姓名:{{studentName}}</h2>
 59                     <h2>学生年龄:{{age}}</h2>
 60                 </div>
 61             `,
 62             data(){
 63                 return {
 64                     studentName:'张三',
 65                     age:18
 66                 }
 67             }
 68         })
 69         
 70         //第一步:创建hello组件
 71         const hello = Vue.extend({
 72             template:`
 73                 <div>    
 74                     <h2>你好啊!{{name}}</h2>
 75                 </div>
 76             `,
 77             data(){
 78                 return {
 79                     name:'Tom'
 80                 }
 81             }
 82         })
 83         
 84         //第二步:全局注册组件
 85         Vue.component('hello',hello)
 86 
 87         //创建vm
 88         new Vue({
 89             el:'#root',
 90             data:{
 91                 msg:'你好啊!'
 92             },
 93             //第二步:注册组件(局部注册)
 94             components:{
 95                 school,    //简写,school:school,key为组件名
 96                 student
 97             }
 98         })
 99         new Vue({
100             el:'#root2',
101         })
102     </script>
103 </html>

 

 

 

Vue中使用组件的三大步骤:
1、定义组件(创建组件)
2、注册组件
3、使用组件(写组件标签)

一、如何定义一个组件?
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:
1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
2.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
备注:使用template可以配置组件结构。

二、如何注册组件?
1.局部注册:靠new Vue的时候传入components选项
2.全局注册:靠Vue.component('组件名',组件)

三、编写组件标签:
<school></school>

 

几个注意点
1.关于组件名
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2).可以使用name配置项指定组件在开发者工具中呈现的名字。

2.关于组件标签
第一种写法:<school></school>
第二种写法:<school/>
备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。

3.一个简写方式
const school = Vue.extend(options) 可简写为:const school = options ,系统会自动调用extend方法

 

2、组件嵌套

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>组件的嵌套</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="../js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 准备好一个容器-->
11         <div id="root">
12             
13         </div>
14     </body>
15 
16     <script type="text/javascript">
17         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
18 
19         //定义student组件
20         const student = Vue.extend({
21             name:'student',
22             template:`
23                 <div>
24                     <h2>学生姓名:{{name}}</h2>    
25                     <h2>学生年龄:{{age}}</h2>    
26                 </div>
27             `,
28             data(){
29                 return {
30                     name:'尚硅谷',
31                     age:18
32                 }
33             }
34         })
35         
36         //定义school组件
37         const school = Vue.extend({
38             name:'school',
39             template:`
40                 <div>
41                     <h2>学校名称:{{name}}</h2>    
42                     <h2>学校地址:{{address}}</h2>    
43                     <student></student>
44                 </div>
45             `,
46             data(){
47                 return {
48                     name:'尚硅谷',
49                     address:'北京'
50                 }
51             },
52             //注册组件(局部)
53             components:{
54                 student
55             }
56         })
57 
58         //定义hello组件
59         const hello = Vue.extend({
60             template:`<h1>{{msg}}</h1>`,
61             data(){
62                 return {
63                     msg:'欢迎来到尚硅谷学习!'
64                 }
65             }
66         })
67         
68         //定义app组件,一人之下万人之上
69         const app = Vue.extend({
70             template:`
71                 <div>    
72                     <hello></hello>
73                     <school></school>
74                 </div>
75             `,
76             components:{
77                 school,
78                 hello
79             }
80         })
81 
82         //创建vm
83         new Vue({
84             template:'<app></app>',
85             el:'#root',
86             //注册组件(局部)
87             components:{app}
88         })
89     </script>
90 </html>

 

 

3、关于VueComponent


1.school组件本质是一个构造函数VueComponent,且不是程序员定义的,是Vue.extend生成的。

2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。

3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!

4.关于this指向:
  (1).组件配置中:
    data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
  (2).new Vue(options)配置中:
    data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。

5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm。

 

4、组件原型对象

 

 

 

 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset="UTF-8" />
 5         <title>一个重要的内置关系</title>
 6         <!-- 引入Vue -->
 7         <script type="text/javascript" src="../js/vue.js"></script>
 8     </head>
 9     <body>
10         <!-- 
11             1.一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
12             2.为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
13         -->
14         <!-- 准备好一个容器-->
15         <div id="root">
16             <school></school>
17         </div>
18     </body>
19 
20     <script type="text/javascript">
21         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
22         Vue.prototype.x = 99
23 
24         //定义school组件
25         const school = Vue.extend({
26             name:'school',
27             template:`
28                 <div>
29                     <h2>学校名称:{{name}}</h2>    
30                     <h2>学校地址:{{address}}</h2>    
31                     <button @click="showX">点我输出x</button>
32                 </div>
33             `,
34             data(){
35                 return {
36                     name:'尚硅谷',
37                     address:'北京'
38                 }
39             },
40             methods: {
41                 showX(){
42                     console.log(this.x)
43                 }
44             },
45         })
46 
47         //创建一个vm
48         const vm = new Vue({
49             el:'#root',
50             data:{
51                 msg:'你好'
52             },
53             components:{school}
54         })
55         
56         //定义一个构造函数
57         /* function Demo(){
58             this.a = 1
59             this.b = 2
60         }
61         //创建一个Demo的实例对象
62         const d = new Demo()
63 
64         console.log(Demo.prototype) //显示原型属性
65 
66         console.log(d.__proto__) //隐式原型属性
67 
68         console.log(Demo.prototype === d.__proto__)
69 
70         //程序员通过显示原型属性操作原型对象,追加一个x属性,值为99
71         Demo.prototype.x = 99
72 
73         console.log('@',d) */
74 
75     </script>
76 </html>

 

 

 让组件实例的原型对象的原型对象指向Vue的原型对象而不是直接指向Object,使得组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

 

19、单文件组件 

 非单文件组件的弊病就是样式不能跟着组件走,而单文件组件把模板样式和交互都集合到一起了。

 

20、Vue CLI

1、创建Vue脚手架

 Vue CLI即Vue 脚手架,它是 Vue 官方提供的标准化开发工具(开发平台)。

下载安装使用:

如出现下载缓慢请配置 npm 淘宝镜像:npm config set registry https://registry.npm.taobao.org

 

第一步(仅第一次执行):全局安装@vue/cli。
npm install -g @vue/cli
第二步:切换到你要创建项目的目录,然后使用命令创建项目
vue create xxxx

出现Vue版本选择提示

 babel将ES6转为ES5,eslint是代码语法检查。

 我这里选择Vue2.

Vue CLI的版本是4.x


第三步:启动项目

在项目目录下:npm run serve

项目就创建完成。

 

 

 

 

 

2、分析脚手架结构

我的第一个项目结构:

 

 

 

├── node_modules:  各种模块
├── public
│ ├── favicon.ico:   页签图标
│ └── index.html:   主页面
├── src
│ ├── assets:   存放静态资源
│ │ └── logo.png
│ │── component:   存放组件
│ │ └── School.vue:   组件
│ │── App.vue:   汇总所有组件
│ │── main.js:   入口文件
├── .gitignore: git   版本管制忽略的配置
├── babel.config.js: babel   的配置文件
├── package.json:   应用包配置文件
├── README.md:   应用描述文件
├── package-lock.json:  包版本控制文件

 main.js:

 

 

App.vue :

 

 

School.vue:

 

 Student.vue:

 

index.html:

 

 启动运行后访问页面:

 

 

3、关于render函数

 

 如果将实例构建写成常规形式而不用render,如下:

 

 

 则会报错:正在使用没有模板解析器的Vue。

 

 

为何如此?

因为import Vue from 'vue'引入的Vue,是vue.runtime.esm.js,残缺版的vue,没有模板解析器的Vue。生产阶段不需要模板解析器,这样做可以精简项目体量。

 

render: h => h(App)其实是下面render函数的简写形式:

render(createElement) {
  return createElement(App)
}

 createElement也是一个函数。

 

总结

关于不同版本的Vue:

1.vue.js与vue.runtime.xxx.js的区别:
(1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
(2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。

2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用
render函数接收到的createElement函数去指定具体内容。

 

4、修改配置文件

https://cli.vuejs.org/zh/config/#vue-config-js

在项目根目录下新建vue.config.js,里面去配置一些允许被更改的配置项

如page的入口,模板的来源:

 

或者关闭语法检查:lintOnSave:false 

注意,跟pages:{}是平级的。

5、ref属性

ref 被用来给元素或子组件注册引用信息(id的替代者)。应用在html标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象(vc)

使用方式:
1. 打标识:```<h1 ref="xxx">.....</h1>``` 或 ```<School ref="xxx"></School>```
2. 获取:```this.$refs.xxx```

如:

 

 

 

6、props配置

 

1. 功能:让组件接收外部传过来的数据

2. 传递数据:```<Demo name="xxx"/>```  ,传表达式记得用单项数据绑定 v-bind:name="obj"

3. 接收数据:

1. 第一种方式(只接收):```props:['name'] ```

2. 第二种方式(限制类型):```props:{name:String}```

3. 第三种方式(限制类型、限制必要性、指定默认值):

```js
props:{
  name:{
    type:String, //类型
    required:false, //必要性
    default:'老王' //默认值
  }
}
```

 

注意:

  所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

  主要是因为在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。对于对象而言,改变对象里的部分属性的值又不会被认为是改变了对象,主要是地址没变,这样又不会报错警告,浅层的监视,而不是类似于watch里的深度监测deep。

  若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

  尽量不要直接修改prop!

 1 <template>
 2     <div>
 3         <h1>{{msg}}</h1>
 4         <h2>学生姓名:{{name}}</h2>
 5         <h2>学生性别:{{sex}}</h2>
 6         <h2>学生年龄:{{myAge+1}}</h2>
 7         <button @click="updateAge">尝试修改收到的年龄</button>
 8     </div>
 9 </template>
10 
11 <script>
12     export default {
13         name:'Student',
14         data() {
15             console.log(this)
16             return {
17                 msg:'我是一个尚硅谷的学生',
18                 myAge:this.age
19             }
20         },
21         methods: {
22             updateAge(){
23                 this.myAge++
24             }
25         },
26         //简单声明接收
27         // props:['name','age','sex'] 
28 
29         //接收的同时对数据进行类型限制
30         /* props:{
31             name:String,
32             age:Number,
33             sex:String
34         } */
35 
36         //接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
37         props:{
38             name:{
39                 type:String, //name的类型是字符串
40                 required:true, //name是必要的
41             },
42             age:{
43                 type:Number,
44                 default:99 //默认值
45             },
46             sex:{
47                 type:String,
48                 required:true
49             }
50         }
51     }
52 </script>

 

 

7、mixin混入

 

功能:可以把多个组件共用的配置提取成一个混入对象。

使用方式:

第一步定义混合:新建一个mixin.js文件,写入混入对象

```
{
  data(){....},
  methods:{....}
....
}
```

第二步使用混入:

​ 全局混入:```Vue.mixin(xxx)```
​ 局部混入:```mixins:['xxx'] ```

 

 如mixin.js :

 

 

 

 组件Student:

 

 

 

 组件School:

 

 

 

 

8、插件

 

1. 功能:用于增强Vue

2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

3. 定义插件:在src目录下创建plugins.js :对象包含install方法

 1 export default {
 2     install(Vue,x,y,z){
 3         console.log(x,y,z)
 4         //全局过滤器
 5         Vue.filter('mySlice',function(value){
 6             return value.slice(0,4)
 7         })
 8 
 9         //定义全局指令
10         Vue.directive('fbind',{
11             //指令与元素成功绑定时(一上来)
12             bind(element,binding){
13                 element.value = binding.value
14             },
15             //指令所在元素被插入页面时
16             inserted(element,binding){
17                 element.focus()
18             },
19             //指令所在的模板被重新解析时
20             update(element,binding){
21                 element.value = binding.value
22             }
23         })
24 
25         //定义混入
26         Vue.mixin({
27             data() {
28                 return {
29                     x:100,
30                     y:200
31                 }
32             },
33         })
34 
35         //给Vue原型上添加一个方法(vm和vc就都能用了)
36         Vue.prototype.hello = ()=>{alert('你好啊')}
37     }
38 }

 

4. 使用插件:先引入再应用

 

 

 

然后就能在组件上使用了。

9、scoped样式

1. 作用:为组件css指定作用域,让样式在组件内部生效,防止多个组件样式名称冲突。
2. 写法:

 

10、todolist案例

 

 实现一个todolist功能的案例,能够新增任务,也能够删除任务,标记已完成任务:

 

 

用组件来实现,初步分析分为三大组件,组件2的每个Item仍然可以分为一个子组件,具体代码实现:

 

App.vue

  1 <template>
  2     <div id="root">
  3         <div class="todo-container">
  4             <div class="todo-wrap">
  5                 <MyHeader :addTodo="addTodo"/>
  6                 <MyList :todos="todos" :checkTodo='checkTodo' :deleteTodo='deleteTodo'></MyList>
  7                 <MyFooter :todos="todos" :checkAll='checkAll' :clearAllTodo='clearAllTodo'></MyFooter>
  8             </div>
  9         </div>
 10     </div>
 11 
 12 </template>
 13 
 14 <script>
 15     import MyHeader from './components/MyHeader.vue'
 16     import MyFooter from './components/MyFooter.vue'
 17     import MyList from './components/MyList.vue'
 18     export default {
 19         name: 'App',
 20         data(){
 21             return{
 22                 todos:[
 23                     // 一般id都是哈希值字符串,不要用数字
 24                     {id:'001',title:'吃饭',done:false},
 25                     {id:'002',title:'睡觉',done:true},
 26                     {id:'003',title:'玩游戏',done:false},
 27                     {id:'005',title:'购物',done:true},
 28                 ]
 29             }
 30         },
 31         methods:{
 32             // 为了实现子组件向父组件传递数据,父组件先向子组件传个函数,通过形参来接收子组件传递进来的实参数据
 33             //添加一个todo
 34             addTodo(todoObj){
 35                 // console.log("我是APP组件,我收到的参数是:",todoObj)
 36                 this.todos.unshift(todoObj)
 37             },
 38             //勾选状态变更时
 39             checkTodo(id){
 40                 this.todos.forEach((todo)=>{
 41                     if(todo.id === id) todo.done=!todo.done
 42                 })
 43             },
 44             //删除一个todo
 45             deleteTodo(id){
 46                 // 方法一:循环遍历
 47                 // this.todos.forEach((todo,index,todos)=>{
 48                 //     if(todo.id === id) todos.splice(index,1)
 49                 // }) 
 50                 
 51                 //方法二:过滤替换为新数组
 52                 this.todos = this.todos.filter((todo)=>{
 53                     return todo.id !== id
 54                 })
 55                 // this.todos = this.todos.filter(todo=>todo.id !==id) 简写模式
 56             },
 57             //全选操作
 58             checkAll(isAll){
 59                 this.todos.forEach((todo)=>{
 60                     todo.done=isAll
 61                 })
 62             },
 63             //删除所有已经完成的todo
 64             clearAllTodo(){
 65                 this.todos = this.todos.filter(todo=>!todo.done)
 66             }
 67         },
 68         components: {
 69             MyFooter,
 70             MyHeader,
 71             MyList
 72         }
 73     }
 74 </script>
 75 
 76 <style>
 77     /* body */
 78     body {
 79         background: #fff;
 80     }
 81     .btn {
 82         display: inline-block;
 83         padding: 4px 12px;
 84         margin-bottom: 0;
 85         font-size: 14px;
 86         line-height: 20px;
 87         text-align: center;
 88         vertical-align: middle;
 89         cursor: pointer;
 90         box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
 91         border-radius: 4px;
 92     }
 93     .btn-danger {
 94         color: #fff;
 95         background-color: #da4f49;
 96         border: 1px solid #bd362f;
 97     }
 98     .btn-danger:hover {
 99         color: #fff;
100         background-color: #bd362f;
101     }
102     .btn:focus {
103         outline: none;
104     }
105     .todo-container {
106         width: 600px;
107         margin: 0 auto;
108     }
109     .todo-container .todo-wrap {
110         padding: 10px;
111         border: 1px solid #ddd;
112         border-radius: 5px;
113     }
114     
115 
116 </style>

 

 

 

 组件1:MyHeader:

 1 <template>
 2     <div class="todo-header">
 3         <label><input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model.trim="title" @keyup.enter="add"/></label>
 4         <button class="btn btn-danger" style="margin-top: 20px;" @click="add">添加任务</button>
 5     </div>
 6 </template>
 7 
 8 <script>
 9     import {nanoid} from 'nanoid'
10     export default{
11         name:'MyHeader',
12         data(){
13             return{
14                 title:''
15             }
16         },
17         props:['addTodo'],
18         methods:{
19             add(){
20                 if(!this.title) return alert('输入内容不能为空!')
21                 // console.log(event.target.value)
22                 const todoObj = {id:nanoid(),title:this.title,done:false}
23                 this.addTodo(todoObj)
24                 this.title=''
25                 
26             }
27         }
28     }
29 </script>
30 
31 <style scoped>
32     /* header */
33     .todo-header input {
34         width: 560px;
35         height: 28px;
36         font-size: 14px;
37         border: 1px solid #ccc;
38         border-radius: 4px;
39         padding: 4px 7px;
40     }
41 
42     .todo-header input:focus {
43         outline: none;
44         border-color: rgba(82, 168, 236, 0.8);
45         box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
46     }
47 </style>

 

 

 组件3:MyFooter:

 1 <template>
 2     <div class="todo-footer">
 3         <label>
 4             <!-- <input type="checkbox" :checked="isAll" @change="checkAllOrNo"/> -->
 5             <!-- checked有初始值并且会变化,直接用v-model -->
 6             <input type="checkbox" v-model="isAll"/>
 7         </label>
 8         <span>
 9             <span>已完成{{doneTotal}}</span> / 全部{{total}}
10         </span>
11         <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
12     </div>
13 </template>
14 
15 <script>
16     export default{
17         name:'MyFooter',
18         props:['todos','checkAll','clearAllTodo'],
19         computed:{
20             total(){
21                 return this.todos.length
22             },
23             //计算已经完成的todo项
24             doneTotal(){
25                 // 简单实现方法:
26                 // return this.todos.filter(todo=>todo.done).length
27                 // 高级实现方法:reduce()
28                 // reduce()方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。
29                 return this.todos.reduce((pre,todo)=>pre+ (todo.done?1:0),0)
30             },
31             // 所有todo全部完成则勾选
32             // 计算属性简写,只读取
33             // isAll(){
34             //     return this.total === this.doneTotal && this.doneTotal>0
35             //     // return this.doneTotal > 0?this.total === this.doneTotal:false
36             // }
37             
38             //计算属性全写,有setter,适用于v-model
39             isAll:{
40                 get(){
41                     return this.total === this.doneTotal && this.doneTotal>0
42                 },
43                 set(value){
44                     this.checkAll(value)
45                 }
46             }
47         },
48         //用v-model之后就不用方法了
49         // methods:{
50         //     checkAllOrNo(e){
51         //         this.checkAll(e.target.checked)
52         //     }
53         // }
54         methods:{
55             clearAll(){
56                 if (confirm('确定要清除所有的已完成任务吗?')) this.clearAllTodo()
57             }
58         }
59     }
60 </script>
61 
62 <style scoped>
63     /*footer*/
64     .todo-footer {
65         height: 40px;
66         line-height: 40px;
67         padding-left: 6px;
68         margin-top: 5px;
69     }
70 
71     .todo-footer label {
72         display: inline-block;
73         margin-right: 20px;
74         cursor: pointer;
75     }
76 
77     .todo-footer label input {
78         position: relative;
79         top: -1px;
80         vertical-align: middle;
81         margin-right: 5px;
82     }
83 
84     .todo-footer button {
85         float: right;
86         margin-top: 5px;
87     }
88 </style>

 

 

 

 组件2:MyList:

 

 1 <template>
 2     <ul class="todo-main">
 3         <MyItem 
 4             v-for="todoObj in todos" 
 5             :key="todoObj.id" 
 6             :todo='todoObj'
 7             :checkTodo='checkTodo'
 8             :deleteTodo='deleteTodo'></MyItem>
 9     
10     </ul>
11 </template>
12 
13 <script>
14     import MyItem from './MyItem.vue'
15     export default{
16         name:'MyList',
17         components:{MyItem},
18         props:['todos','checkTodo','deleteTodo'],
19     }
20 </script>
21 
22 <style scoped>
23     /*main*/
24     .todo-main {
25         margin-left: 0px;
26         border: 1px solid #ddd;
27         border-radius: 2px;
28         padding: 0px;
29     }
30 
31     .todo-empty {
32         height: 40px;
33         line-height: 40px;
34         border: 1px solid #ddd;
35         border-radius: 2px;
36         padding-left: 5px;
37         margin-top: 10px;
38     }
39 </style>

 

 

 组件2-1:MyItem:

 

 1 <template>
 2     <div >
 3         <li>
 4             <label>
 5                 <!-- 这里不推荐用v-model,因为其会改变父组件的数据 -->
 6                 <!-- <input type="checkbox" v-model='todo.done'/> -->
 7                 <input type="checkbox" :checked='todo.done' @change="handleCheck(todo.id)"/>
 8                 <!-- 这里不推荐用todo.done=!todo.done来实现勾选状态的交互,同样是因为其会改变父组件的数据 -->
 9                 <!-- <input type="checkbox" :checked='todo.done' @click="todo.done=!todo.done"/> -->
10 
11                 <span>{{todo.title}}</span>
12             </label>
13             <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
14         </li>
15     </div>
16 </template>
17 
18 <script>
19     export default{
20         name:'MyItem',
21         // 声明接收todo对象
22         props:['todo','checkTodo','deleteTodo'],
23         methods:{
24             handleCheck(id){
25                 // 通知App组件将对应的todo对象的done值取反
26                 this.checkTodo(id)
27             },
28             handleDelete(id){
29                 if(confirm("确认删除吗?")) this.deleteTodo(id)
30             }
31         },
32         mounted() {
33             // MyList组件prop传对象表达式记得用单项数据绑定,不然传的就是字符串了
34             // console.log(this.todo)
35             // console.log(typeof(this.todo))
36         }
37     }
38 </script>
39 
40 <style scoped>
41     /*item*/
42     li {
43         list-style: none;
44         height: 36px;
45         line-height: 36px;
46         padding: 0 5px;
47         border-bottom: 1px solid #ddd;
48     }
49 
50     li label {
51         float: left;
52         cursor: pointer;
53     }
54 
55     li label li input {
56         vertical-align: middle;
57         margin-right: 6px;
58         position: relative;
59         top: -1px;
60     }
61 
62     li button {
63         float: right;
64         display: none;
65         margin-top: 3px;
66     }
67 
68     li:before {
69         content: initial;
70     }
71 
72     li:last-child {
73         border-bottom: none;
74     }
75 
76     li:hover{
77         background-color: #ddd;
78     }
79     
80     li:hover button{
81         display: block;
82     }
83 </style>

 

 

 main.js:

 

 1 //引入Vue
 2 import Vue from 'vue'
 3 //引入App
 4 import App from './App.vue'
 5 //关闭Vue的生产提示
 6 Vue.config.productionTip = false
 7 
 8 //创建vm
 9 new Vue({
10     el:'#app',
11     render: h => h(App)
12 })

 

 

案例总结:

组件化编码流程:

​ (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

​ (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

​   1).一个组件在用:放在组件自身即可。

​   2). 一些组件在用:放在他们共同的父组件上(状态提升)。

​ (3).实现交互:从绑定事件开始。

props适用于:

​ (1).父组件 ==> 子组件 通信

​ (2).子组件 ==> 父组件 通信(要求父先给子一个函数)

使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

 

 

11、组件的自定义事件

区别于js的给元素用的内置事件,组件的自定义事件是给组件用的。

App.vue:

 1 <template>
 2     <div class="app">
 3         <h1>{{msg}}</h1>
 4         <!-- 通过父组件给子组件传递函数类型的props来实现:子给父传递数据 -->
 5         <School :getSchoolName="getSchoolName"/>
 6         <!-- 通过给子组件的实例对象VC绑定自定义事件来实现:子给父传递数据 ,难点在于如何触发该事件-->
 7         <Student v-on:atguigu="getStudentName"/>
 8         <!-- 一次性 -->
 9         <!-- <Student v-on:atguigu.once="getStudentName"/> -->
10         
11         <!-- 上面绑定事件的另一种实现方法:ref ,$refs.student就是VC-->
12         <!-- <Student ref="student"/> -->
13         
14         <!-- 给组件实例绑定原生的点击事件:要加native,不然就被认为是自定义事件click -->
15         <!-- <Student @click.native='show'/> -->
16     </div>
17 </template>
18 
19 <script>
20     import Student from './components/Student'
21     import School from './components/School'
22 
23     export default {
24         name:'App',
25         components:{School,Student},
26         data(){
27             return{
28                 msg:'天青色!'
29             }
30         },
31         methods:{
32             getSchoolName(name){
33                 console.log('APP收到了school的名字了',name)
34             },
35             /* getStudentName(name,){
36                 console.log('APP收到了student的名字了',name)
37             }, */
38             
39             // 多个参数的情形:
40             getStudentName(name,...params){
41                 console.log('APP收到了student的名字了',name,params)
42             },
43             
44             show(){
45                 alert(666)
46             }
47         },
48         mounted() {
49             // 当student VC身上的自定义事件atguigu被触发的时候执行回调函数getStudentName
50             // this.$refs.student.$on('atguigu',this.getStudentName)
51             // this.$refs.student.$once('atguigu',this.getStudentName)
52             
53             // 为何用这种方法实现呢?因为它比较灵活,比如要等3秒后才绑定atguigu事件:
54             // setTimeout(()=>{this.$refs.student.$on('atguigu',this.getStudentName)}, 3000);
55             
56         }
57     }
58 </script>
59 
60 <style scoped>
61     .app{
62         background-color: gray;
63     }
64 </style>

 

school.vue:

 1 <template>
 2     <div class="school">
 3         <h2>学校名称:{{name}}</h2>
 4         <h2>学校地址:{{address}}</h2>
 5         <button @click="sendSchoolName">把学校名给App</button>
 6     </div>
 7 </template>
 8 
 9 <script>
10     export default {
11         name:'School',
12         props:['getSchoolName'],
13         data() {
14             return {
15                 name:'尚硅谷',
16                 address:'北京',
17             }
18         },
19         methods:{
20             sendSchoolName(){
21                 this.getSchoolName(this.name)
22             }
23         }
24     }
25 </script>
26 
27 <style scoped>
28     .school{
29         background-color: skyblue;
30         padding: 10px;
31     }
32 </style>

 

 

student.vue:

 1 <template>
 2     <div class="school">
 3         <h2>学生姓名:{{name}}</h2>
 4         <h2>学生性别:{{sex}}</h2>
 5         <button @click="sendStudentName">把学生名传给App</button>
 6         <button @click="unbind">解除绑定的自定义事件</button>
 7         <button @click="death">销毁组件实例vc</button>
 8     </div>
 9 </template>
10 
11 <script>
12     export default {
13         name:'Student',
14         data() {
15             return {
16                 name:'张三',
17                 sex:''
18             }
19         },
20         methods:{
21             sendStudentName(){
22                 // 触发自定义事件并传参
23                 // this.$emit('atguigu',this.name)
24                 
25                 // 传递多个参数的情形:
26                 this.$emit('atguigu',this.name,111,222,333)
27             },
28             //解绑自定义事件
29             unbind(){
30                 this.$off('atguigu')
31                 // this.$off(['atguigu',xxx]) 解绑多个
32                 // this.$off() 解绑全部
33             },
34             death(){
35                 this.$destroy() //销毁组件实例则其绑定的自定义事件都没了
36             }
37         }
38     }
39 </script>
40 
41 <style scoped>
42     .school{
43         background-color: pink;
44         padding: 10px;
45         margin-top: 1.25rem;
46     }
47 </style>

 

 

 

在上一节todolist案例中,子组件向父组件传递数据是通过props传递回调函数实现的,现在可以用自定义组件来实现。通过给子组件的实例对象VC绑定自定义事件来实现子给父传递数据。

.

总结:

1、一种组件间通信的方式,适用于:子组件 ===> 父组件

2、使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

3、绑定自定义事件:

  第一种方式,在父组件中:<Demo @atguigu="test"/> 或 <Demo v-on:atguigu="test"/>

  第二种方式,在父组件中:

    <Demo ref="demo"/> ...... mounted(){ this.$refs.xxx.$on('atguigu',this.test) }

4、若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

5、触发自定义事件:this.$emit('atguigu',数据)

6、解绑自定义事件this.$off('atguigu')

7、组件上也可以绑定原生DOM事件,需要使用native修饰符。

注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

 

 

12、全局事件总线

通过设置全局事件总线,可以实现任意组件间数据通信。

 

 

 

 本来应该在vc的原型对象上添加总线bus,但这样一来每个vc都要这么做,所以去vm的原型对象上添加,因为vc在自己的原型对象上找不到bus就会去vm的原型对象上寻找(详见组件那一节)。

 

例如要实现兄弟组件school和student之间的通信:

在school组件上绑定总线事件,在student组件上触发该事件,传入数据,然后执行school的回调函数,接收数据。

 

谁要接收数据,谁就在挂载后绑定事件。 

 

 

 

 

 点击按钮,运行结果:

 

 

 在组件销毁时记得去解绑事件。

 

13、todolist案例补充编辑功能

 

 

 

 在组件MyItem中增加编辑按钮和输入框,点击后输入框自动获取焦点,修改完毕后按enter或者失去焦点则保存更改,显示更改后的数据。在编辑模式中编辑按钮隐藏,且输入不能为空。

 

 

 

 

 

 在App里面给事件总线绑定一个自定义事件,添加回调函数,实行数据的修改。

 

14、配置代理

 

 如果前端应用和后端 API 服务器没有运行在同一个主机上,你需要在开发环境下将 API 请求代理到 API 服务器。不配置的话跨域请求是不可以的。协议名、域名和端口号都相同才算同一个主机。

 

 

方法一

​ 在vue.config.js中添加如下配置:

devServer:{
  proxy:"http://localhost:5000"
}

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)

方法二

​ 编写vue.config.js配置具体代理规则:

module.exports = {
    devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api1': ''} // 访问所代理的目标服务器时去除/api1
      },
      '/api2': {// 匹配所有以 '/api2'开头的请求路径
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。

 

              Vue CLI 的配置参考里也有提及:

 

 

 

 

 也可以在后端通过cors解决跨域问题。

 

 

15、插槽

作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。

分类:默认插槽、具名插槽、作用域插槽

使用方式:

1、默认插槽

父组件中:
        <Category>
           <div>html结构1</div>
        </Category>
子组件中:
        <template>
            <div>
               <!-- 定义插槽 -->
               <slot>插槽默认内容...</slot>
            </div>
        </template>

 

 2、具名插槽

父组件中:
        <Category>
            <template slot="center">
              <div>html结构1</div>
            </template>

            <template v-slot:footer>
               <div>html结构2</div>
            </template>
        </Category>
子组件中:
        <template>
            <div>
               <!-- 定义插槽 -->
               <slot name="center">插槽默认内容...</slot>
               <slot name="footer">插槽默认内容...</slot>
            </div>
        </template>

 

 

3、作用域插槽

理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

 

父组件中:
        <Category>
            <template scope="scopeData">
                <!-- 生成的是ul列表 -->
                <ul>
                    <li v-for="g in scopeData.games" :key="g">{{g}}</li>
                </ul>
            </template>
        </Category>

        <Category>
            <template slot-scope="scopeData">
                <!-- 生成的是h4标题 -->
                <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
            </template>
        </Category>
子组件中:
        <template>
            <div>
                <slot :games="games"></slot>
            </div>
        </template>

        <script>
            export default {
                name:'Category',
                props:['title'],
                //数据在子组件自身
                data() {
                    return {
                        games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                    }
                },
            }
        </script>

 

21、Vuex

 

22、路由

1、什么是路由?

 

  一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。前端路由:key是路径,value是组件。

 

2、安装路由器

安装:npm i vue-router@3  (学的是vue2,所以安装版本3的vue-router ,vue3则直接npm i vue-router)

 

3、基本使用

在应用中创建路由器:src/router/index.js

 

  1. 编写router配置项:

    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入路由组件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
        routes:[
            {
                path:'/about',
                component:About
            },
            {
                path:'/home',
                component:Home
            }
        ]
    })
    
    //暴露router
    export default router
  2. 实现切换(active-class可配置高亮样式)

    <router-link active-class="active" to="/about">About</router-link>
  3. 指定展示位置

    <router-view></router-view>

 

main.js :

 

 

 

注意:

路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹:

 

 

通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。

 

 

vc身上都有route和router:

 

 不同的组件实例身上的$route属性是不同的,里面储存这自己的路由信息,但$router是相同的,整个应用只有一个$router:

 

 

 

4、多级路由

  1. 配置路由规则,使用children配置项:

    routes:[
        {
            path:'/about',
            component:About,
        },
        {
            path:'/home',
            component:Home,
            children:[ //通过children配置子级路由
                {
                    path:'news', //此处一定不要写:/news
                    component:News
                },
                {
                    path:'message',//此处一定不要写:/message
                    component:Message
                }
            ]
        }
    ]
  2. 跳转(要写完整路径):

    <router-link to="/home/news">News</router-link>

5、路由query传参

直接在to中追加是不可以的,都当字符串处理:

 

 

跳转路由并携带query参数的正确写法:

1、to的字符串写法:

 

2、to的对象写法:

 

 建议使用第二种写法,看得舒服些。

传递的参数都在vc上$route属性的query里面:

 

 

 拿到参数:

 

6、命名路由

给路由添加一个name属性,如:

 

 

 

 跳转的时候就可以直接写名字,而不用写长长的三级路径:

 

 

7、路由params传参

 

 1、占位

 

 

 2、跳转

 

注意写法,以‘/’ 来拼接的。

另一种对象写法:

 

注意:不能使用path配置项,必须使用name配置! 

具体用哪种写法,随意。

 3、用参

 

 

 

 

 

 

 

8、路由props配置

之前在组件内使用参数时是通过$route来获取的,但是这种方法会导致组件和路由的紧密耦合,这会限制组件的灵活性。官方文档(4x版本)中也有说明:

 

 

并且对于消息详情页,如果除了消息编号和标题,还有其他的消息属性,那就要写很多,也不太方便

 

这个时候就需要用到路由的props配置。

作用:让路由组件更方便的收到参数,并且让组件和路由之间不产生耦合。

{
    name:'xiangqing',
    path:'detail/:id',
    component:Detail,

    //第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
    // props:{a:900}

    //第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
    // props:true

    //第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
    props(route){
        return {
            id:route.query.id,
            title:route.query.title
        }
    }
}

推荐使用函数形式的props值:

 简写:

 

 

 接收并使用参数:

 

9、<router-link>的replace属性

 

作用:控制路由跳转时操作浏览器历史记录的模式

浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,类似于压栈,replace是替换当前记录。路由跳转时候默认为push。

如何开启replace模式:<router-link replace .......>News</router-link>

 

以下是replace:

 

 

 

10、编程式路由导航

 

  1. 作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活

  2. 具体编码:

    //$router的两个API
    this.$router.push({
        name:'xiangqing',
            params:{
                id:xxx,
                title:xxx
            }
    })
    
    this.$router.replace({
        name:'xiangqing',
            params:{
                id:xxx,
                title:xxx
            }
    })
    this.$router.forward() //前进
    this.$router.back() //后退
    this.$router.go() //可前进也可后退,正数前进,负数后退

 

 

 

 

 

 如果连续点击上面的push查看按钮或者replace查看按钮会报错:

 

 

报的是重复路由的错误。

解决:可在route下面的index.js中写入以下代码,修改原型对象中的push方法:

 

 

 

11、缓存路由组件

如下,在输入框输入数据然后切换到其他组件,再切回来会发现数据丢失,因为切出去后原来的组件就被销毁了:

 

 

 

 有些信息如个人身份信息,客户填了半天切出去再切回来发现全没了,太不友好了!那么怎么才能让它不销毁呢?

News组件是展示在Home组件内部的,可以将Home组件的展示区用keep-alive标签包起来,这样就不会销毁了,include属性指定不被销毁的组件名,如果没有,则Home组件下的所有组件都不会被销毁。

 

缓存多个组件::include="['News','Message']" 

 

12、两个新的生命周期钩子

在组件消息列表下面添加一行,实现汉字天青色渐变:

 

 

 

 

 

 但是这样有一个问题,当切出组件News,因为有缓存,News就不会被销毁,定时器也就不会被销毁,定时器就一直在执行。

 解决办法:用到两个新的钩子函数activated和deactivated

 

 

展示某个组件时即被激活,切出去则失活。

 

13、全局前置路由守卫

 当我们需要给news和message页面添加权限验证:

 

 

 可以在路由器中使用全局前置路由守卫:

 

初始化时或者每次路由切换之前都会执行beforeEach()方法,通过if判断满足条件后才能访问,即next()

 这里的条件是本地存储中有name=tqs。

 不过如果有多个条件判断则写起来比较麻烦,可以在路由规则里的meta对象中添加鉴权标识isAuth,值为true则表示要鉴权。

 

 

14、全局后置路由守卫

 如果要实现每次切换组件时标题跟着切换,则需要用到后置路由守卫。

 

 路由规则添加title:

 

 

后置路由守卫设置页面title:

 

 

15、独享路由守卫

直接写在某个路由规则里面

16

 

 

 

 

 

 

 

 

未完待续

 

posted @ 2022-01-24 15:32  天青色wy  阅读(108)  评论(0编辑  收藏  举报