Vue2.0+Vue3.0

1. vue基础知识和原理

  1.1 初识Vue

  • 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
  • demo容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
  • demo容器里的代码被称为【Vue模板】
  • Vue实例和容器是一一对应的
  • 真实开发中只有一个Vue实例,并且会配合着组件一起使用
  • {xxx}}是Vue的语法:插值表达式,{{xxx}}可以读取到data中的所有属性
  • 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新(Vue实现的响应式)
 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>初识Vue</title>
 8     <!-- 引入vue -->
 9     <script type="text/javascript" src="../js/vue.js"></script>
10 </head>
11 <body>
12     <div id="root">
13         <h1>hello,{{name.toUpperCase()}},{{address}}</h1>
14     </div>
15    <script type="text/javascript">
16    Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
17     //创建Vue实例
18    new Vue({
19     el:'#root',//el用于指定当前Vue实例为那个容器服务,值通常为css选择器字符串
20     data:{  //data用于存储数据,供el所指定容器使用
21         name:'zhangsan',
22         address:'北京'
23     } 
24     });
25     </script> 
26 </body>
27 </html>

1.2模板语法

Vue模板语法有2大类:

插值语法:

功能:用于解析标签体内容

写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性

指令语法:

功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)

举例:v-bind:href=“xxx” 或 简写为 :href=“xxx”,xxx同样要写js表达式,且可以直接读取到data中的所有属性

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>模板语法</title>
 8     <!-- 引入vue -->
 9     <script type="text/javascript" src="../js/vue.js"></script>
10 </head>
11 <body>
12     <div id="root">
13         <h1>插值语法</h1>
14         <h2>你好,{{name}}</h2>
15         <hr/>
16         <h1>指令语法</h1>
17         <a v-bind:href="url">去百度</a>
18         <!-- v-bind可以简写为 : -->
19         <a :href="school.url">去{{school.name}}</a> 
20     </div>
21     <script type="text/javascript">
22     Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
23     new Vue({
24             el:'#root',
25             data:{
26                 name:'李四',
27                 url:'http://www.baidu.com',
28                 school:{
29                 name:'百度',
30                 url:'http://www.baidu.com'
31             }
32             },
33             
34         })
35     </script>
36    
37 </body>
38 </html>

1.3数据绑定

Vue中有2种数据绑定的方式:

单向绑定(v-bind):数据只能从data流向页面

双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data

tips:

1.双向绑定一般都应用在表单类元素上(如:input、select等)

2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>数据绑定</title>
    <script  src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
        单向数据绑定:<input type="text" v-bind:value="name"><br/>
        单向数据绑定:<input type="text" v-model:value="name"><br/>
        <!-- 简写 -->
        单向数据绑定:<input type="text" :value="name"><br/>
        单向数据绑定:<input type="text" v-model="name"><br/>
    </div>

    <script>
        Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
        new Vue({
            el:'#root',
            data:{
               name:'小马哥'
            },
        })
    </script>
</body>
</html>

1.4 el与data的两种写法

el有2种写法

  • new Vue时候配置el属性

  • 先创建Vue实例,随后再通过vm.$mount(’#root’)指定el的值

    data有2种写法

    • 对象式

    • 函数式

      data有2种写法

      • 对象式

      • 函数式

        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>el与data的两种写法</title>
            <script src="../js/vue.js"></script>
        </head>
        <body>
            <div id="root">
                <h1>你好,{{name}}</h1>
            </div>
        
            <script>
                Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
                const v=new Vue({
                    //el:'#root',//第一种写法
                    //data的第一种写法
                    // data:{
                    //    name:'小马哥'
                    // },
                    data() {
                        return {
                            name:'小马哥'  
                        }
                    },
                })
                console.log(v)
                v.$mount('#root')//第二种写法
            </script>
        </body>
        </html>

        在组件中,data必须使用函数式

1.5 Vue中的MVVM

  • M:模型(Model) :data中的数据
  • V:视图(View) :模板代码
  • VM:视图模型(ViewModel):Vue实例

1.6 数据代理

Object.defineProperty(obj, prop, descriptor)

obj:要定义属性的对象。

prop:要定义或修改的属性的名称

descriptor:要定义或修改的属性描述符

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>1.object.defineProperty方法</title>
 8 </head>
 9 <body>
10     <script type="text/javascript">
11         let number=18
12         let person ={
13             name:'小马哥',
14             sex:'',
15             //age:12
16         }
17 
18         Object.defineProperty(person,'age',{
19             // value:10,
20             // enumberable:true,//控制属性是否可以枚举
21             // writable:true,//控制属性是否可以被修改
22             // configurable:true,//控制属性是否可以被删除
23 
24             get(){
25                 console.log('已读取')
26                 return number
27             },
28 
29             set(value){
30                 console.log('已修改')
31                 number=value
32                 
33             }
34         })
35     </script>
36 </body>
37 </html>

数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)

简单例子:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>简单数据代理例子</title>
 8 </head>
 9 <body>
10     <script>
11         let obj1={x:100}
12         let obj2={y:200}
13 
14         Object.defineProperty(obj2,'x',{
15             get(){
16                 return obj1.x
17             },
18             set(value){
19                 obj1.x=value
20             }
21         })
22     </script>
23 </body>
24 </html>

接下来介绍Vue中的数据代理

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

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>Vue中的数据代理</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <h1>学校名称:{{name}}</h1>
13         <h1>学校地址:{{address}}</h1>
14     </div>
15     <script>
16          Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
17          const vm=new Vue({
18             el:'#root',
19             data() {
20                 return {
21                     name:'小马',
22                     address:'天堂'
23                 }
24             },
25          })
26     </script>
27 </body>
28 </html>

1.7 事件处理

事件的基本使用:

  • 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名
  • 事件的回调需要配置在methods对象中,最终会在vm上
  • methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象
 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>事件的基本使用</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <h2>欢迎来到{{}}学习</h2>
13         <!-- 创建点击事件 -->
14         <!-- <button v-on:click="showInfo">点我提示信息</button> -->
15         <!-- v-on可简写为@ -->
16         <button @click="showInfo1">Message1(不传参)</button><br>
17         <button @click="showInfo2($event,66)">Message2(传参,用$event站位,不会丢失event)</button>
18         
19     </div>
20 
21     <script>
22          Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
23         new Vue({
24             el:'#root',
25             data:{
26                name:'小马哥课堂'
27             },
28             methods: {
29                 showInfo1(event){
30                     //console.log(event.target.innerText)//获得点击标签
31                     //console.log(this)//此处的this是vm
32                     alert('同学你好')
33                 },
34                 showInfo2(event,number){
35                     console.log(number)
36                 }
37             },
38             
39         })
40     </script>
41 </body>
42 </html>

 

Vue中的事件修饰符

  • prevent:阻止默认事件(常用)
  • stop:阻止事件冒泡(常用)
  • once:事件只触发一次(常用)
 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>事件修饰符</title>
 8     <script src="../js/vue.js"></script>
 9     <style>
10         *{
11             margin-top: 20px;
12         }
13         .demo1{
14             height: 50px;
15             background-color: aqua;
16         }
17     </style>
18 </head>
19 <body>
20     <div id="root">
21         <h1>欢迎来到{{name}}学习</h1>
22         <!-- 阻止默认事件 -->
23         <a href="http://www.baidu.com" @click.prevent="showInfo">点我提示信息</a>
24         <!-- 阻止事件冒泡  -->
25         <div class="demo1" @click="showInfo">
26             <button  @click.stop="showInfo">点我提示信息</button>
27         </div>
28         <!-- 事件只触发一次 -->
29         <button  @click.once="showInfo">点我提示信息</button>
30     </div>
31     <script>
32         Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
33         new Vue({
34             el:'#root',
35             data:{
36              name:'小马哥课堂'  
37             },
38             methods: {
39                 showInfo(event){
40                     alert('同学你好')
41                 }
42             },
43         })
44     </script>
45 </body>
46 </html>

1.8 键盘事件

键盘事件语法糖:@keydown,@keyup

Vue中常用的按键别名:

  • 回车 => enter
  • 删除 => delete
  • 退出 => esc
  • 空格 => space
  • 换行 => tab (特殊,必须配合keydown去使用)
 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>键盘事件</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <h1>欢迎来到{{name}}学习</h1>
13         <input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo">
14     </div>
15 
16     <script>
17          Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
18          new Vue({
19             el:'#root',
20             data:{
21                name:'杰马' 
22             },
23             methods: {
24                 showInfo(e){
25                    console.log(e.target.value) 
26                 }  
27             },
28          })
29     </script>
30     
31 </body>
32 </html>

1.9 计算属性

姓名案例;

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>姓名案例——插值语法</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
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.slice(0,3)}}-{{lastName}}</span>
15     </div>
16     <script>
17          Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
18          new Vue({
19             el:'#root',
20             data:{
21                 firstName:'',
22                 lastName:''
23             }
24          })
25     </script>
26 </body>
27 </html>
 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>姓名案例——methods方法</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
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     <script>
17          Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
18          new Vue({
19             el:'#root',
20             data:{
21                 firstName:'',
22                 lastName:''
23             },
24             methods: {
25                 fullName(){
26                     return this.firstName +'-'+ this.lastName
27                 }
28             },
29          })
30     </script>
31 </body>
32 </html>

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

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>姓名案例——计算属性实现</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
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     <script>
17          Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
18          new Vue({
19             el:'#root',
20             data:{
21                 firstName:'',
22                 lastName:''
23             },
24             computed:{
25                 fullName:{
26                 //get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
27                 //get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
28                     get(){
29                       return this.firstName +'-'+ this.lastName
30                     },
31                 //set什么时候调用? 当fullName被修改时。
32                 //可以主动在控制台修改fullName来查看情况
33                 set(value){
34                     console.log('set',value)
35                     const arr = value.split('-')
36                     this.firstName = arr[0]
37                     this.lastName = arr[1]
38                 }
39                 }
40             }
41          })
42     </script>
43 </body>
44 </html>

计算属性简写

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>姓名案例——计算属性实现</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
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     <script>
17          Vue.config.productionTip = false;//阻止vue在启动时生成生产提示
18          new Vue({
19             el:'#root',
20             data:{
21                 firstName:'',
22                 lastName:''
23             },
24             computed:{
25                 fullName(){
26                     return this.firstName +'-'+ this.lastName
27                 }
28             }
29          })
30     </script>
31 </body>
32 </html>

1.10 监视属性

天气案例

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>天气案例</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <h2>今天天气很{{info}}</h2>
13         <button @click="changeWeather">切换天气</button>
14     </div>
15 
16     <script>
17         new Vue({
18             el:'#root',
19             data:{
20               isHot:true  
21             },
22 
23             computed:{
24                 info(){
25                     return this.isHot ? '炎热' : '凉爽'
26                 }
27             },
28             methods: {
29                 changeWeather(){
30                     this.isHot=!this.isHot
31                 }
32             },
33         })
34     </script>
35 </body>
36 </html>

监视属性watch:

  • 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
  • 监视的属性必须存在,才能进行监视
  • 监视的两种写法:(1).new Vue时传入watch配置(2).通过vm.$watch监视
  •  1 <!DOCTYPE html>
     2 <html lang="en">
     3 <head>
     4     <meta charset="UTF-8">
     5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     7     <title>天气案例_监视属性</title>
     8     <script src="../js/vue.js"></script>
     9 </head>
    10 <body>
    11     <div id="root">
    12         <h2>今天天气很{{info}}</h2>
    13         <button @click="changeWeather">切换天气</button>
    14     </div>
    15 
    16     <script>
    17         new Vue({
    18             el:'#root',
    19             data:{
    20               isHot:true  
    21             },
    22 
    23             computed:{
    24                 info(){
    25                     return this.isHot ? '炎热' : '凉爽'
    26                 }
    27             },
    28             methods: {
    29                 changeWeather(){
    30                     this.isHot=!this.isHot
    31                 }
    32             },
    33             watch:{
    34                 isHot:{
    35                     handler(newValue,oldValue){
    36                         console.log('isHot被修改了',newValue,oldValue)
    37                     }
    38                 }
    39             }
    40         })
    41     </script>
    42 </body>
    43 </html>

    深度监视:

    • (1).Vue中的watch默认不监测对象内部值的改变(一层)(2).配置deep:true可以监测对象内部值改变(多层)

  备注:

  (1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以

  (2).使用watch时根据数据的具体结构,决定是否采用深度监视

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>天气案例_深度监视</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <h2>今天天气很{{info}}</h2>
13         <button @click="changeWeather">切换天气</button>
14         <hr>
15         <h3>a的值是{{numbers.a}}</h3>
16         <button @click="adda">点击使a+1</button>
17         <h3>b的值是{{numbers.b}}</h3>
18         <button @click="addb">点击使b+1</button>
19     </div>
20 
21     <script>
22         new Vue({
23             el:'#root',
24             data:{
25               isHot:true,
26               numbers:{
27                 a:1,
28                 b:1
29               } 
30             },
31 
32             computed:{
33                 info(){
34                     return this.isHot ? '炎热' : '凉爽'
35                 }
36             },
37             methods: {
38                 changeWeather(){
39                     this.isHot=!this.isHot
40                 },
41                 adda(){
42                     return this.numbers.a++
43                 },
44                 addb(){
45                     return this.numbers.b++
46                 }
47             },
48             watch:{
49                 isHot:{
50                     handler(newValue,oldValue){
51                         console.log('isHot被修改了',newValue,oldValue)
52                     }
53                 },
54                 // //监视多级属性中某个属性的变化
55                 // 'numbers.a':{
56                 //     handler(){
57                 //         console.log('a被改变了')
58                 //     }
59                 // }
60 
61                 //监视多级属性中所有属性的变化
62                 numbers:{
63                     deep:true,//开启深度监视
64                     handler(){
65                         console.log('a被改变了')
66                     }
67                 }
68             }
69         })
70     </script>
71 </body>
72 </html>

 

computed和watch之间的区别:

  • computed能完成的功能,watch都可以完成
  • watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作

两个重要的小原则:

1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象

2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象

1.11 绑定样式

class样式

写法::class=“xxx” xxx可以是字符串、对象、数。

所以分为三种写法,字符串写法,数组写法,对象写法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>绑定样式</title>
    <script src="../js/vue.js"></script>
    <style>
        .basic{
            width: 400px;
            height: 100px;
            border: 1px solid black;
        }
        .happy{
            border: 4px solid red;
            background-color: rgba(255, 255, 0, 0.644);
            background: linear-gradient(30deg,yellow,pink,orange,yellow);
        }
        .sad{
            border: 4px dashed rgb(2, 197, 2);
            background-color: gray;
        }
        .normal{
            background-color: skyblue;
        }
        .atjiema1{
            background-color: yellowgreen;
        }
        .atjiema2{
            font-size: 30px;
            text-shadow: 2px 2px 10px red;
        }
        .atjiema3{
            border-radius: 20px;
        }
    </style>
</head>
<body>
    <!-- 准备好一个容器-->
<div id="root">
    <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
    <div class="basic" :class="mood" @click="changeMood" >{{name}}</div><br><br>

    <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定,名字也不确定 -->
    <div class="basic" :class="arr" @click="changeMood2">{{name}}</div><br><br>

    <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定,名字确定,但要动态决定用不用 -->
    <div class="basic" :class="classObj">{{name}}</div>

</div>

<script>
     const vm =new Vue({
        el:'#root',
        data:{
            name:'杰马',
            mood:'normal',
            arr:['atjiema1','atjiema2','atjiema3'],
            classObj:{
                atjiema1:true,
                atjiema2:false
            }
        },
        methods: {
            changeMood(){
                //this.mood='happy'

                //随机生成
                const arr=['happy','sad','normal']
                const index=Math.floor(Math.random()*3)
                this.mood=arr[index]
            },
            changeMood2(){
                //this.mood='happy'

                //随机生成
                const arr=['atjiema1','atjiema2','atjiema3']
                const index=Math.floor(Math.random()*3)
                this.arr=arr[index]
            },
        },
    })
</script>

</body>
</html>

1.12 条件渲染
v-if
写法:

(1).v-if=“表达式”

(2).v-else-if=“表达式”

(3).v-else=“表达式”

适用于:切换频率较低的场景

特点:不展示的DOM元素直接被移除

注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”

 

v-show

  • 写法:v-show=“表达式”
  • 适用于:切换频率较高的场景
  • 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉(display:none)

备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到

v-if 是实打实地改变dom元素,v-show 是隐藏或显示dom元素

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>Document</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <!-- 使用v-show做条件渲染 -->
13         <!-- <h2 v-show="false">欢迎来到{{name}}</h2> -->
14         <!-- <h2 v-show="1 === 2">欢迎来到{{name}}</h2> -->
15 
16         <!-- 使用v-if做条件渲染 -->
17         <!-- <h2 v-if="false">欢迎来到{{name}}</h2> -->
18 
19         <h2>当前n的值是{{n}}</h2>
20         <button @click="add()">点击后n+1</button>
21 
22         <!-- <div v-show="n===1">Angular</div>
23              <div v-show="n===2">React</div>
24              <div v-show="n===3">Vue</div> -->
25         
26         <!-- 使用v-if做条件渲染 -->
27         <!-- <div v-if="n===1">Angular</div>
28         <div v-if="n===2">React</div>
29         <div v-if="n===3">Vue</div> -->
30 
31         <div v-if="n===1">Angular</div>
32         <div v-else-if="n===2">React</div>
33         <div v-else-if="n===3">Vue</div>
34         <div v-else>哈哈,学完了</div>
35     </div>
36 
37     <script>
38         const vm= new Vue({
39             el:'#root',
40             data:{
41                 name:'杰马集团',
42                 //a:false
43                 n:0
44             },
45             methods: {
46                 add(){
47                     return this.n++
48                 }
49             },
50         })
51     </script>
52     
53 </body>
54 </html>

1.13 列表渲染

v-for指令

  • 用于展示列表数据
  • 语法:v-for="(item, index) in xxx" :key=“yyy”
  • 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>基本列表</title>
    <script src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
        <h2>人员列表</h2>
        <ul>
            <li v-for="p in persons" :key="p.id">
                {{p.name}}-{{p.age}}
            </li>
        </ul>

        <h2>汽车信息</h2>
        <ul>
            <li v-for="(value,k) of car" :key="k">
                {{k}}-{{value}}
            </li>
        </ul>

        <h2>测试遍历字符串</h2>
        <ul>
            <li v-for="(char,index) of str" :key="index">
                {{char}}-{{index}}
            </li>
        </ul>
    </div>

    <script>
        new Vue({
            el:'#root',
            data:{
                persons:[
                    {id:'001',name:'张三',age:'18'},
                    {id:'002',name:'李四',age:'19'},
                    {id:'003',name:'王五',age:'20'}
                ],
                car:{
                    name:'特斯拉',
                    price:'50W',
                    color:'黑色'
                },
                str:'hello'
            },    
        })
    </script>
</body>
</html>

key的原理

虚拟DOM中key的作用
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。

用index作为key可能会引发的问题:

若对数据进行:逆序添加、逆序删除等破坏顺序操作:

会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低

结论:

  • 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值
  • 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的

 列表过滤

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>列表过滤</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <h2>人员列表</h2>
13         <input type="text" placeholder="请输入名字" v-model="keyWorld">
14         <ul>
15             <li v-for="(p,index) in filPersons" :key="p.id">
16                 {{p.name}}-{{p.age}}-{{p.sex}}
17             </li>
18         </ul>
19     </div>
20 
21     <script>
22         //用watch实现
23         //#region  
24         // new Vue({
25         //     el:'#root',
26         //     data:{
27         //         keyWorld:'',
28         //         persons:[
29         //             {id:'001',name:'马冬梅',age:'18',sex:'女'},
30         //             {id:'002',name:'周冬雨',age:'19',sex:'女'},
31         //             {id:'003',name:'周杰伦',age:'20',sex:'男'},
32         //             {id:'003',name:'温兆伦',age:'21',sex:'男'}
33         //         ],
34         //         filPersons:[]//存放过滤出来的数据
35         //     },    
36             
37         //     watch:{
38         //         keyWorld:{
39         //             immediate:true,
40         //             handler(val){
41         //                 this.filPersons = this.persons.filter((p)=>{ //filter过滤
42         //                 return p.name.indexOf(val) != -1 //indexOf判断是否包含
43         //             })
44         //             }
45                     
46         //         }
47         //     }
48         // })
49         //
50         //#endregion
51     
52         //用computed实现
53         new Vue({
54             el:'#root',
55             data:{
56                 keyWorld:'',
57                 persons:[
58                     {id:'001',name:'马冬梅',age:'18',sex:''},
59                     {id:'002',name:'周冬雨',age:'19',sex:''},
60                     {id:'003',name:'周杰伦',age:'20',sex:''},
61                     {id:'003',name:'温兆伦',age:'21',sex:''}
62                 ],
63             },    
64             
65             computed:{
66                 filPersons(){
67                     return this.filPersons=this.persons.filter((p)=>{
68                         return p.name.indexOf(this.keyWorld) !== -1
69                     })
70                 }
71             }
72         })
73     </script>
74 </body>
75 </html>

 列表排序

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>列表过滤</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <h2>人员列表</h2>
13         <input type="text" placeholder="请输入名字" v-model="keyWorld">
14         <button @click="sortType=1">按年龄降序</button>
15         <button @click="sortType=2">按年龄升序</button>
16         <button @click="sortType=0">原顺序</button>
17         <ul>
18             <li v-for="(p,index) in filPersons" :key="p.id">
19                 {{p.name}}-{{p.age}}-{{p.sex}}
20             </li>
21         </ul>
22     </div>
23 
24     <script>
25         //用computed实现
26         new Vue({
27             el:'#root',
28             data:{
29                 keyWorld:'',
30                 sortType:0,//0原顺序 1降序 2升序
31                 persons:[
32                     {id:'001',name:'马冬梅',age:'18',sex:''},
33                     {id:'002',name:'周冬雨',age:'31',sex:''},
34                     {id:'003',name:'周杰伦',age:'6',sex:''},
35                     {id:'003',name:'温兆伦',age:'40',sex:''}
36                 ],
37             },    
38             
39             computed:{
40                 filPersons(){
41                     const arr= this.filPersons=this.persons.filter((p)=>{
42                         return p.name.indexOf(this.keyWorld) !== -1
43                     })
44                     if(this.sortType){
45                         arr.sort((p1,p2)=>{
46                             return this.sortType === 1 ? p2.age-p1.age : p1.age-p2.age
47                         })
48                     }
49                     return arr
50                 }
51             }
52         })
53     </script>
54 </body>
55 </html>

1.14 vue 监测data 中的 数据

先来个案例引入一下:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>数据更新时的问题</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <h2>人员列表</h2>
13         <button @click="updatema">点我更新信息</button>
14         <ul>
15             <li v-for="(p,index) in persons" :key="p.id">
16                 {{p.name}}-{{p.age}}-{{p.sex}}
17             </li>
18         </ul>
19     </div>
20 
21     <script>
22         //用computed实现
23         new Vue({
24             el:'#root',
25             data:{
26                 persons:[
27                     {id:'001',name:'马冬梅',age:'18',sex:''},
28                     {id:'002',name:'周冬雨',age:'31',sex:''},
29                     {id:'003',name:'周杰伦',age:'6',sex:''},
30                     {id:'003',name:'温兆伦',age:'40',sex:''}
31                 ],
32             }, 
33             methods: {
34                 updatema(){
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     </script>
44 </body>
45 </html>

点击更新马冬梅的信息,马冬梅的数据并没有发生改变。

我们来看看控制台:

控制台上的数据发生了改变,说明,这个更改的数据并没有被 vue 监测到。

所以我们来研究一下 Vue 监测的原理。

我们先研究 Vue 如何监测 对象里的数据

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>Vue检测数据变化的原理_对象</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <h2>学校名称:{{name}}</h2>
13         <h2>学校地址:{{address}}</h2>
14     </div>
15     
16     <script type="text/javascript">
17         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
18     
19         const vm = new Vue({
20             el:'#root',
21             data:{
22                 name:'杰马课堂',
23                 address:'天堂',
24                 student:{
25                     name:'tom',
26                     age:{
27                         rAge:40,
28                         sAge:29,
29                     },
30                     friends:[
31                         {name:'jerry',age:35}
32                     ]
33                 }
34             }
35         })
36     </script>
37 </body>
38 </html>

讲一下解析模板后面的操作—》调用 set 方法时,就会去解析模板----->生成新的虚拟 DOM----->新旧DOM 对比 -----> 更新页面

模拟一下 vue 中的 数据监测

 1 <script type="text/javascript" >
 2 
 3     let data = {
 4         name:'尚硅谷',
 5         address:'北京',
 6     }
 7 
 8     //创建一个监视的实例对象,用于监视data中属性的变化
 9     const obs = new Observer(data)        
10     console.log(obs)    
11 
12     //准备一个vm实例对象
13     let vm = {}
14     vm._data = data = obs
15 
16     function Observer(obj){
17         //汇总对象中所有的属性形成一个数组
18         const keys = Object.keys(obj)
19         //遍历
20         keys.forEach((k) => {
21             Object.defineProperty(this, k, {
22                 get() {
23                     return obj[k]
24                 },
25                 set(val) {
26                     console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
27                     obj[k] = val
28                 }
29             })
30         })
31     }
32 </script>

 

Vue.set 的使用

Vue.set(target,propertyName/index,value) 或

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

用法:

向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 vm.myObject.newProperty = 'hi')

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>Vue.set的使用</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <h1>学校信息</h1>
13         <button @click="seeLeader">点击查看校长信息</button>
14         <h2>学校名称:{{name}}</h2>
15         <h2>学校地址:{{address}}</h2>
16         <h2 v-if="school.leader">校长是:{{school.leader}}</h2>
17         <hr>
18         <h1>学生信息</h1>
19         <button @click="addSex">点击添加性别信息</button>
20         <h2>学生姓名:{{student.name}}</h2>
21         <h2 v-if="student.sex">性别:{{student.sex}}</h2>
22         <h2>学生年龄:真实{{student.age.rAge}} 对外:{{student.age.sAge}}</h2>
23         <h2>朋友们</h2>
24         <ul>
25             <li v-for="(f,index) in student.friends" ::key="index">
26             {{f.name}}-{{f.age}}
27             </li>
28         </ul>
29 
30     </div>
31     
32     <script type="text/javascript">
33         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
34     
35         const vm = new Vue({
36             el:'#root',
37             data:{
38                 school:{
39                     name:'杰马课堂',
40                     address:'天堂',
41                 },
42                 student:{
43                     name:'tom',
44                     age:{
45                         rAge:40,
46                         sAge:29,
47                     },
48                     friends:[
49                         {name:'jerry',age:35},
50                         {name:'jack',age:36}
51                     ]
52                 }
53             },
54             methods: {
55                 addSex(){
56                     Vue.set(this.student,'sex','')
57                 },
58                 seeLeader(){
59                     Vue.set(this.school,'leader','马传庆')
60                 }
61             },
62         })
63     </script>
64 </body>
65 </html>

总结:

Vue监视数据的原理:

  • vue会监视data中所有层次的数据
  • 如何监测对象中的数据?

通过setter实现监视,且要在new Vue时就传入要监测的数据。

  • 对象中后追加的属性,Vue默认不做响应式处理
  • 如需给后添加的属性做响应式,请使用如下API:

  Vue.set(target,propertyName/index,value) 或

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

  • 如何监测数组中的数据?

  通过包裹数组更新元素的方法实现,本质就是做了两件事:

  • 调用原生对应的方法对数组进行更新
  • 重新解析模板,进而更新页面
  • 在Vue修改数组中的某个元素一定要用如下方法:

    使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
          Vue.set() 或 vm.$set()

1.15 收集表单数据

若:<input type="text">,则v-model收集的是value值,用户输入的就是value值。

若:<input type="radio">,则v-model收集的是value值,且要给标签配置value值。

若:<input type="checkbox">

  1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
  2.配置input的value属性:
    v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
    v-model的初始值是数组,那么收集的的就是value组成的数组

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>收集表单数据</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <form @submit.prevent="demo">
13             账号:<input type="text" v-model.trim="userInfo.account"><br><br>
14             密码:<input type="password" v-model.trim="userInfo.password"><br><br>
15             性别:
16<input type="radio" name="sex" v-model="userInfo.sex" value="male">
17<input type="radio" name="sex" v-model="userInfo.sex" value="female"><br><br> 
18             年龄:
19             <input type="number" v-model.number="userInfo.age"><br><br> 
20             爱好:
21             抽烟<input type="checkbox" v-model= "userInfo.hobby" value="smoking">
22             喝酒<input type="checkbox" v-model= "userInfo.hobby" value="drinke">
23             烫头<input type="checkbox" v-model= "userInfo.hobby" value="tangtou"><br><br>
24             所属校区
25             <select v-model="userInfo.city">
26                 <option value="">请选择校区</option>
27                 <option value="beijing">北京</option>
28                 <option value="nanjing">南京</option>
29                 <option value="dongjing">东京</option>
30                 <option value="xijing">西京</option>
31                 <option value="tianjing">天津</option>
32             </select><br><br>
33             其他信息:
34             <textarea v-model.lazy="userInfo.other"></textarea><br><br>
35             <input type="checkbox" v-model="userInfo.agree">阅读并接受《用户协议》</a></a><a href="http://www.baidu.com"></a><br><br>
36             <button>注册</button>
37         </form>
38     </div>
39 
40     <script>
41         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
42         new Vue({
43             el:'#root',
44             data:{
45                 userInfo:{
46                     account:'',
47                     password:'',
48                     sex:'',
49                     hobby:[],
50                     city:'',
51                     other:'',
52                     agree:'',
53                     age:''
54                 }
55         },
56         methods: {
57             demo(){
58                 alert('注册成功'),
59                 console.log(JSON.stringify(this.userInfo))//JSON格式打印到控制台
60             }
61         },
62         })
63         
64     </script>
65 </body>
66 </html>

备注:v-model的三个修饰符:

lazy:失去焦点再收集数据

number:输入字符串转为有效的数字

trim:输入首尾空格过滤

1.17 内置指令

  学过的指令:

    v-bind:单向绑定解析表达式,可简写为:xxx

    v-model:双向数据绑定

    v-for:遍历数组/对象/字符串

    v-on:绑定事件监听,可简写为@

    v-if:条件渲染(动态控制节点是否存在)

    v-else:条件渲染(动态控制节点是否存在)

    v-show:条件渲染(动态控制节点是否展示)

v-text指令:(使用的比较少)

1.作用:向其所在的节点中渲染文本内容。

2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>v-text</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <div>{{name}}</div>
13         <div v-text="name"></div>
14     </div>
15 
16     <script>
17         new Vue({
18             el:'#root',
19             data:{
20                 name:'杰马集团'
21             }
22         })
23     </script>
24 </body>
25 </html>

v-html指令:(使用的很少)

1.作用:向指定节点中渲染包含html结构的内容。

2.与插值语法的区别:

v-html会替换掉节点中所有的内容,{{xx}}则不会。
v-html可以识别html结构。
3.严重注意:v-html有安全性问题!!!!

在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>v-html指令</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <!-- 准备好一个容器-->
12 <div id="root">
13     <div>你好,{{name}}</div>
14     <div v-html="str"></div>
15     <div v-html="str2"></div>
16 </div>
17 
18 <script type="text/javascript">
19     Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
20 
21     new Vue({
22         el:'#root',
23         data:{
24             name:'张三',
25             str:'<h3>你好啊!</h3>',
26             str2:'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>',
27         }
28     })
29 </script>
30 
31 </body>
32 </html>

v-cloak指令(没有值):

  • 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
  • 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
 1 <style>
 2     [v-cloak]{
 3         display:none;
 4     }
 5 </style>
 6 <!-- 准备好一个容器-->
 7 <div id="root">
 8     <h2 v-cloak>{{name}}</h2>
 9 </div>
10 <script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
11 
12 <script type="text/javascript">
13     console.log(1)
14     Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
15 
16     new Vue({
17         el:'#root',
18         data:{
19             name:'尚硅谷'
20         }
21     })
22 </script>

v-once指令:(用的少)

  • v-once所在节点在初次动态渲染后,就视为静态内容了。
  • 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
 1 <!-- 准备好一个容器-->
 2 <div id="root">
 3     <h2 v-once>初始化的n值是:{{ n }}</h2>
 4     <h2>当前的n值是:{{ n }}</h2>
 5     <button @click="n++">点我n+1</button>
 6 </div>
 7 
 8 <script type="text/javascript">
 9     Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
10 
11     new Vue({
12         el:'#root',
13         data:{
14             n:1
15         }
16     })
17 </script>

v-pre指令:(比较没用)

  • 跳过其所在节点的编译过程
  • 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
 1 <!-- 准备好一个容器-->
 2 <div id="root">
 3     <h2 v-pre>Vue其实很简单</h2>
 4     <h2 >当前的n值是:{{n}}</h2>
 5     <button @click="n++">点我n+1</button>
 6 </div>
 7 
 8 <script type="text/javascript">
 9     Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
10 
11     new Vue({
12         el:'#root',
13         data:{
14             n:1
15         }
16     })
17 </script>

 

1.18 自定义指令

需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。

需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>自定义指令</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <h2>当前的n值是: <span v-text="n"></span></h2>
13         <h2>放大10倍后n值是: <span v-big="n"></span></h2>
14         <button @click="n++">n+1</button>
15         <hr>
16         <input type="text" v-fbind:value="n">
17     </div>
18     
19     <script>
20         new Vue({
21             el:'#root',
22             data:{
23                 n:1
24             },
25             directives:{
26                 //big函数何时会被调用?1.指令与元素绑定时2.指令所在的模板被重新解析时
27                 big(element,binding){//element:指令所在的元素 binding:本次绑定的信息
28                     element.innerText=binding.value *10
29 
30                 },
31                 fbind:{
32                     //bind:指令与元素成功绑定时调用。
33                     bind(element,binding){
34                         element.value=binding.value
35                     },
36                     //inserted:指令所在元素被插入页面时调用
37                     inserted(element,binding){
38                         element.focus()
39                     },
40                     //update:指令所在模板结构被重新解析时调用
41                     update(element,binding){
42                         element.value=binding.value
43                     }
44                 }
45             }    
46         })
47     </script>
48     
49 </body>
50 </html>

配置对象中常用的3个回调:

  • bind:指令与元素成功绑定时调用。
  • inserted:指令所在元素被插入页面时调用。
  • update:指令所在模板结构被重新解析时调用。

 

自定义指令总结:

  一.定义语法

    1.局部指令:

    new Vue({

      directives:{指令名,配置对象}

  })或

    

    new Vue({

      directives{指令名,回调函数}

  })

    2.全局指令

    Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)

  二.备注

    1.指令定义时不加v-,但使用时要加v-

    2.指令名如果是多个单词,要使用kebab-case命名,不要用camelCase命名

 

定义全局指令

 1 <!-- 准备好一个容器-->
 2 <div id="root">
 3     <input type="text" v-fbind:value="n">
 4 </div>
 5 
 6 <script type="text/javascript">
 7     Vue.config.productionTip = false
 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     new Vue({
26         el:'#root',
27         data:{
28             name: '尚硅谷',
29             n: 1
30         }
31     })
32 
33 </script>

局部指令

 1 new Vue({
 2     el: '#root',
 3     data: {
 4         name:'尚硅谷',
 5         n:1
 6     },
 7     directives: {
 8         // big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
 9         /* 'big-number'(element,binding){
10                     // console.log('big')
11                     element.innerText = binding.value * 10
12                 }, */
13         big (element,binding){
14             console.log('big',this) //注意此处的this是window
15             // console.log('big')
16             element.innerText = binding.value * 10
17         },
18         fbind: {
19             //指令与元素成功绑定时(一上来)
20             bind (element,binding){
21                 element.value = binding.value
22             },
23             //指令所在元素被插入页面时
24             inserted (element,binding){
25                 element.focus()
26             },
27             //指令所在的模板被重新解析时
28             update (element,binding){
29                 element.value = binding.value
30             }
31         }
32     }
33 })

 

1.19 生命周期

简介生命周期

  生命周期:

    1.又名:生命周期回调函数,生命周期函数,生命周期钩子

    2.是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数

    3.生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的

    4.生命周期函数中的this指向是vm或组件实例对象

 

Vue 实例有⼀个完整的⽣命周期,也就是从new Vue()、初始化事件(.once事件)和生命周期、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。

beforeCreate(创建前):数据监测(getter和setter)和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。


created(创建后):实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el属性。


beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。此阶段Vue开始解析模板,生成虚拟DOM存在内存中,还没有把虚拟DOM转换成真实DOM,插入页面中。所以网页不能显示解析好的内容


mounted(挂载后):在el被新创建的 vm.$el(就是真实DOM的拷贝)替换,并挂载到实例上去之后调用(将内存中的虚拟DOM转为真实DOM,真实DOM插入页面)。此时页面中呈现的是经过Vue编译的DOM,这时在这个钩子函数中对DOM的操作可以有效,但要尽量避免。一般在这个阶段进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等等


beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染(数据是新的,但页面是旧的,页面和数据没保持同步呢)。


updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。


beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件。

  1. destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

先判断有没有 el 这个配置项,没有就调用 vm.$mount(el),如果两个都没有就一直卡着,显示的界面就是最原始的容器的界面。有 el 这个配置项,就进行判断有没有 template 这个配置项,没有 template 就将 el 绑定的容器编译为 vue 模板

 

这个 template 有啥用咧?

第一种情况,有 template:

如果 el 绑定的容器没有任何内容,就一个空壳子,但在 Vue 实例中写了 template,就会编译解析这个 template 里的内容,生成虚拟 DOM,最后将 虚拟 DOM 转为 真实 DOM 插入页面(其实就可以理解为 template 替代了 el 绑定的容器的内容)。

第二种情况,没有 template:

没有 template,就编译解析 el 绑定的容器,生成虚拟 DOM,后面就顺着生命周期执行下去。

 

总结

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>引出生命周期</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <h2 :style="{opacity}">一起学习Vue</h2>
13         <button @click="stop">点我停止变换</button>
14     </div>
15 
16     <script>
17         new Vue({
18             el:'#root',
19             data:{
20                 opacity:1
21             },
22             methods: {
23                 stop(){
24                    this.$destroy();//vm被干掉,准备调用销毁流程
25                 }
26             },
27 
28             //Vue完成模板解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
29             mounted(){
30             this.timer= setInterval(() => {//定时器:按照指定的周期时间来调用函数或计算表达
31             this.opacity -= 0.01
32             if(this.opacity <= 0) this.opacity = 1
33         }, 16)
34         },
35         beforeDestroy() {//销毁流程
36             clearInterval(this.timer)
37         },
38         })
39 
40     </script>
41 </body>
42 </html>

 

常用的生命周期钩子:

  1.mounted:发送ajax请求,启动定时器,绑定自定义事件,订阅消息等(初始化操作)

  2.beforeDestroy:清除定时器,解绑自定义事件,取消订阅消息等(收尾工作)

 

关于销毁Vue实例:

  1.销毁后借助Vue开发者工具看不到任何信息

  2.销毁后自定义事件会失效,但原生DOM事件依然有效

  3.一般不会在beforeDestroy操作数据,也不会再触发更新流程了

 

1.20 非单文件组件

基本使用

Vue中使用组件的三大步骤:

  • 定义组件(创建组件)
  • 注册组件
  • 使用组件(写组件标签)

 

定义组件

使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;

区别如下:

el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。

注册组件

  • 局部注册:靠new Vue的时候传入components选项
  • 全局注册:靠Vue.component(‘组件名’,组件)

 

编写组件标签

  <school></school>

  1 <!DOCTYPE html>
  2 <html lang="en">
  3 <head>
  4     <meta charset="UTF-8">
  5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7     <title>基本使用</title>
  8     <script src="../js/vue.js"></script>
  9 </head>
 10 <body>
 11     <div id="root">
 12        <!-- 编写组件标签 -->
 13        <school></school>
 14        <hr>
 15        <hello></hello>
 16        <hr>
 17        <student></student>
 18        
 19     </div>
 20     <div id="root2">
 21        <hello></hello>
 22     </div>
 23    
 24 
 25     <script>
 26         //创建school组件
 27         const school = Vue.extend({
 28             template:`
 29             <div>
 30                 <h2>学校名称:{{schoolName}}</h2>
 31                 <h2>学校地址:{{schoolAddress}}</h2>
 32                 <button @click="showName" >点击提示学校名</button>
 33             </div>
 34             `,
 35             data() {
 36                 return {
 37                     schoolName:'杰马课堂',
 38                     schoolAddress:'天堂', 
 39                 }
 40             },
 41             methods: {
 42                 showName(){
 43                     alert(this.schoolName)//弹窗
 44                 }
 45             },
 46         });
 47 
 48         //创建student组件
 49         const student=Vue.extend({
 50             template:`
 51             <div>
 52                 <h2>学生姓名:{{studentName}}</h2>
 53                 <h2>学校地址:{{studentAge}}</h2>
 54             </div>
 55             `,
 56             data() {
 57                 return {
 58                     studentName:'李四',
 59                     studentAge:'18'
 60                 }
 61             },
 62         });
 63 
 64         //创建hello组件
 65         const hello=Vue.extend({
 66             template:`
 67             <div>
 68             <h2>你好啊:{{name}}</h2>
 69         </div>
 70             `,
 71             data() {
 72                 return {
 73                     name:'Tom'
 74                 }
 75             },
 76         })
 77 
 78         //注册组件(全局注册)
 79         Vue.component('hello',hello)//('组件名字',位置)
 80 
 81 
 82 
 83         new Vue({
 84             el:'#root',
 85             //注册组件(局部注册)
 86             components:{
 87                 school,
 88                 student
 89             }
 90         });
 91 
 92         new Vue({
 93             el:'#root2',
 94 
 95         })
 96 
 97 
 98         /* 创建vm
 99         new Vue({
100             el:'#root',
101             data:{
102                 schoolName:'杰马课堂',
103                 schoolAddress:'天堂',
104                 studentName:'李四',
105                 studentAge:'18'
106             },
107         }) */
108     </script>
109 </body>
110 </html>

 

 

几个注意点:

关于组件名:

一个单词组成:

  • 第一种写法(首字母小写):school
  • 第二种写法(首字母大写):School

多个单词组成:

  • 第一种写法(kebab-case命名):my-school
  • 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)

备注:

(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。

(2).可以使用name配置项指定组件在开发者工具中呈现的名字。

 

关于组件标签:

第一种写法:<school></school>

第二种写法:<school/>

备注:不用使用脚手架时,会导致后续组件不能渲染。

一个简写方式:

const school = Vue.extend(options) 可简写为:const school = options

 

组件的嵌套

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>组件的嵌套</title>
 8     <script src="../js/vue.js"></script>
 9 </head>
10 <body>
11     <div id="root">
12         <!-- <app></app> -->
13        
14     </div>
15     
16 
17     <script>
18 
19         //创建hello组件
20         const hello=Vue.extend({
21             template:`
22             <div>
23                 <h1>{{msg}}</h1>
24             </div>
25             `,
26             data() {
27                 return {
28                     msg:'欢迎来到杰马课堂学习'
29                 }
30             },
31         })
32         //定义student组件
33         const student=Vue.extend({
34             template:`
35             <div>
36                 <h3>学生姓名:{{name}}</h3>
37                 <h3>学生年龄:{{age}}</h3>
38             </div>
39             `,
40             data() {
41                 return {
42                     name:'Tom',
43                     age:'18'
44                 }
45             },
46         })
47         //定义school组件
48         const school=Vue.extend({
49             template:`
50             <div>
51             <h2>学校名称:{{name}}</h2>
52             <h2>学校地址:{{address}}</h2>
53             <student></student>
54             </div>
55             `,
56             data() {
57                 return {
58                     name:'杰马课堂',
59                     address:'天堂'
60                 }
61             },
62             //注册组件(局部)
63             components:{
64                 student
65             }
66         });
67 
68         //定义app组件,用来管理其他组件
69         const app=Vue.extend({
70             template:`
71             <div>
72                 <hello></hello>
73                 <school></school>
74             </div>
75             `,
76 
77             components:{
78                 school,
79                 hello
80             }
81         })
82         
83 
84         new Vue({
85             template:`<app></app>`,
86             el:'#root',
87             //注册组件(局部)
88             components:{app}
89         })
90     </script>
91 </body>
92 </html>

VueComponent

  • school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
  • 我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。
  • 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!(这个VueComponent可不是实例对象)
  • 关于this指向:

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

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

一个重要的内置关系

  • 一个重要的内置关系:VueComponent.prototype.proto === Vue.prototype
  • 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

 

1.21 单文件组件

单文件组件就是将一个组件的代码写在 .vue 这种格式的文件中,webpack 会将 .vue 文件解析成 html,css,js这些形式。

School.vue

 1 <template>
 2     <div class="demo">
 3         <h2>学校名称:{{name}}</h2>
 4         <h2>学校地址:{{address}}</h2>
 5         <button @click="showName">点我提示学校名</button>
 6     </div>
 7 </template>
 8     
 9 <script>
10    export default {//默认暴露+创建组件简写
11     name:'School',//组件名
12     data() {
13             return {
14                 name:'杰马课堂',
15                 address:'天堂'
16             }
17         },
18 
19         methods: {
20             showName(){
21                 alert(this.schoolName)
22             }
23         },
24    }
25 
26     // export default school//默认暴露
27 </script>
28 
29 <style>
30     .demo{
31         background-color: darkturquoise
32     }
33 </style>

Student.vue

 1 <template>
 2     <div class="demo">
 3         <h2>学生姓名:{{name}}</h2>
 4         <h2>学生年龄:{{age}}</h2>
 5     </div>
 6 </template>
 7 
 8 <script>
 9    export default {
10     name:'Student',
11     data() {
12         return {
13             name:'Tom',
14             age:18
15         }
16     },
17    }
18 </script>
19 
20 <style>
21     .demo{
22         background-color: blue
23     }
24 </style>

App.vue

用来汇总所有的组件(大总管)

 1 <<template>
 2     <div>
 3         <School></School>
 4         <Student></Student>
 5     </div>
 6 </template>
 7 
 8 <script>
 9     //引入组件
10     import School from './School'
11     import Student from './Student'
12 
13     export default {
14     name: 'App',
15     components:{
16         School,
17         Student
18     },
19 };
20 </script>

main.js

在这个文件里面创建 vue 实例

1 import App from './App.vue'
2 
3 new new Vue({
4     el:'#root',
5     template:`<App></App>`,
6     components:{App},
7 })

index.html

在这写 vue 要绑定的容器

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>单文件组件</title>
 8 </head>
 9 <body>
10     <div id="root">
11         
12     </div>
13     <script src="../js/vue.js"></script>
14     <script src="./main.js"></script>   
15 </body>
16 </html>

 

2. vue脚手架,自定义事件,插槽等复杂内容

2.1 脚手架

使用前置:

第一步(没有安装过的执行):全局安装 @vue/cli

npm install -g @vue/cli

第二步:切换到要创建项目的目录,然后使用命令创建项目

vue create xxxxx

第三步:启动项目

npm run serve

脚手架文件结构

 1 ├── node_modules 
 2 ├── public
 3 │   ├── favicon.ico: 页签图标
 4 │   └── index.html: 主页面
 5 ├── src
 6 │   ├── assets: 存放静态资源
 7 │   │   └── logo.png
 8 │   │── component: 存放组件
 9 │   │   └── HelloWorld.vue
10 │   │── App.vue: 汇总所有组件
11 │   │── main.js: 入口文件
12 ├── .gitignore: git版本管制忽略的配置
13 ├── babel.config.js: babel的配置文件
14 ├── package.json: 应用包配置文件 
15 ├── README.md: 应用描述文件
16 ├── package-lock.json:包版本控制文件

render函数

  插入一个小知识:

    使用 import 导入第三方库的时候不需要 加 ‘./’

    导入我们自己写的:

import App from './App.vue'

    导入第三方的

 1 import Vue from 'vue' 

 

 

之前的写法是这样:

1 import App from './App.vue'
2 
3 new Vue({
4     el:'#root',
5     template:`<App></App>`,
6     components:{App},
7 })

因为 render 函数内并没有用到 this,所以可以简写成箭头函数:

1 new Vue({
2   // render: h => h(App),
3   render: (createElement) => {
4     return createElement(App)
5   }
6 }).$mount('#app')

再简写: 1 new Vue({ 2 // render: h => h(App), 3 render: createElement => createElement(App) 4 }).$mount('#app') 

最后把 createElement 换成 h 就完事了。

  

来个不同版本 vue 的区别

vue.js与vue.runtime.xxx.js的区别:
  vue.js是完整版的Vue,包含:核心功能+模板解析器。
  vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
  因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。

 

修改脚手架的默认配置

  • 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
  • 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh

 

2.2 vue 零碎的一些知识

ref属性

被用来给元素或子组件注册引用信息(id的替代者)
应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
使用方式:
打标识:<h1 ref="xxx">.....</h1>或 <School ref="xxx"></School>
获取:this.$refs.xxx

 1 <template lang="">
 2     <div>
 3         <h1 v-text="msg" ref="title"></h1>
 4         <button @click="showDom">点击输出上方DOM元素</button>
 5         <School></School>
 6     </div>
 7 </template>
 8 <script>
 9     import School from './components/School'
10     export default {
11         name:'App',
12         data() {
13             return {
14                 msg:'欢迎来到杰马'
15             }
16         },
17         methods: {
18             showDom(){
19                 console.log(this.$refs.title)
20             }
21         },
22         // eslint-disable-next-line vue/no-unused-components
23         components:{School}
24     }
25 </script>
26 <style lang="">
27     
28 </style>

 

props配置项

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

  2.传递数据:<Demo name="xxx"/>

  3.接收数据:

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

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

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

 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     // eslint-disable-next-line vue/multi-word-component-names
14     name: 'Student',
15 
16     data() {
17         return {
18             msg:'杰马课堂大弟子',
19             myAge:this.age
20         };
21     },
22     props:['name','age','sex'], //简单声明接收
23 
24     methods: {
25         updateAge(){
26             this.myAge++
27         }
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 };
53 </script>

备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

mixin(混入)

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

编写混入文件

mixin.js

1 export const mixin ={
2     methods: {
3         showName(){
4             alert(this.name)
5         }
6     },
7 
8 }

引入

 1 <template>
 2     <div>
 3         <h2 @click="showName">学校名称:{{name}}</h2>
 4         <h2>学校地址:{{address}}</h2>
 5     </div>
 6 </template>
 7 
 8 <script>
 9     import {mixin} from '../mixin'
10     export default {
11     // eslint-disable-next-line vue/multi-word-component-names
12     name: 'School',
13 
14     data() {
15         return {
16             name:'杰马课堂',
17             address:'天堂'
18         };
19     },
20     mixins:[mixin]
21 };
22 </script>

插件

功能:用于增强Vue

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

定义插件:

  对象.install=function(Vue,options){

    

//1.添加全局过滤器
    Vue.filter(...)
   
    //2.添加全局指令
    Vue.directive('...')

    //3.配置全局混入(合)
    Vue.mixin(...)

    //4.添加实例方法
    Vue.prototype.$method = function (options) {...};
    Vue.prototype.$method =xxx

}

编写插件:plugins.js

 1 export default {
 2     install(Vue){
 3         console.log('@@@install')
 4 
 5         Vue.mixin({
 6             data(){
 7                 return{
 8                     x:100,
 9                     y:200
10                 }
11             },
12         })
13         
14     }
15 }

使用插件:main.js

 1 /* 
 2   该文件是整个项目的入口文件
 3 */
 4 //引入Vue
 5 import Vue from 'vue'
 6 //引入App组件,它是所有组件的父组件
 7 import App from './App.vue'
 8 //引入插件
 9 import plugins from './plugins';
10 //关闭vue的生产提示
11 Vue.config.productionTip=false
12 //应用插件
13 Vue.use(plugins)
14 //创建vue实例对象————vm
15 new Vue({
16     el:'#root',
17     //将app组件放入容器中
18     render:h => h(App)
19 })

 

scoped样式

  1. 作用:让样式在局部生效,防止冲突。
  2. 写法:<style scoped>
 1 <template>
 2     <div class="demo">
 3         <h2>学校名称:{{name}}</h2>
 4         <h2>学校地址:{{address}}</h2>
 5     </div>
 6 </template>
 7 
 8 <script>
 9     export default {
10     // eslint-disable-next-line vue/multi-word-component-names
11     name: 'School',
12 
13     data() {
14         return {
15             name:'杰马课堂',
16             address:'天堂'
17         };
18     },
19     
20 };
21 </script>
22 <style scoped>
23     .demo{
24         background-color: aqua
25     }
26 </style>

 

总结TodoList案例

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" />
  7                 <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
  8             </div>
  9         </div>
 10     </div>
 11 </template>
 12 <script>
 13     import MyHeader from './components/MyHeader'
 14     import MyList from './components/MyList'
 15     import MyFooter from './components/MyFooter'
 16     export default {
 17         name:'App',
 18         // eslint-disable-next-line vue/no-unused-components
 19         components:{
 20             MyHeader,
 21             MyList,
 22             MyFooter,
 23             },
 24             data() {
 25             return {
 26                 todos:[
 27                     {id:'001',title:'抽烟',done:true},
 28                     {id:'002',title:'喝酒',done:false},
 29                     {id:'003',title:'开车',done:true},
 30                 ]
 31             }
 32         },
 33         //     data() {
 34         //     return {
 35         //         //由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
 36         //         todos:JSON.parse(localStorage.getItem('todos')) || []
 37         //     }
 38         // },
 39         methods: {
 40             //添加一个todo
 41             addTodo(todoObj){
 42                 this.todos.unshift(todoObj)
 43             },
 44             //勾选or取消勾选一个todo
 45             checkTodo(id){
 46                 this.todos.forEach((todo)=>{
 47                     if(todo.id === id) todo.done = !todo.done
 48                 })
 49             },
 50             //删除一个todo
 51             deleteTodo(id){
 52                 this.todos = this.todos.filter( todo => todo.id !== id )
 53             },
 54             //全选or取消全选
 55             checkAllTodo(done){
 56                 this.todos.forEach((todo)=>{
 57                     todo.done = done
 58                 })
 59             },
 60             //清除所有已经完成的todo
 61             clearAllTodo(){
 62                 this.todos = this.todos.filter((todo)=>{
 63                     return !todo.done
 64                 })
 65             }
 66         },
 67         /* watch: {
 68             todos:{
 69                 deep:true,
 70                 handler(value){
 71                     localStorage.setItem('todos',JSON.stringify(value))
 72                 }
 73             }
 74         }, */
 75         mounted() {
 76             this.$bus.$on('checkTodo',this.checkTodo)
 77             this.$bus.$on('deleteTodo',this.deleteTodo)
 78         },
 79         beforeDestroy() {
 80             this.$bus.$off('checkTodo')
 81             this.$bus.$off('deleteTodo')
 82         },
 83     }
 84 </script>
 85 
 86 <style>
 87     /*base*/
 88     body {
 89         background: #fff;
 90     }
 91     .btn {
 92         display: inline-block;
 93         padding: 4px 12px;
 94         margin-bottom: 0;
 95         font-size: 14px;
 96         line-height: 20px;
 97         text-align: center;
 98         vertical-align: middle;
 99         cursor: pointer;
100         box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
101         border-radius: 4px;
102     }
103     .btn-danger {
104         color: #fff;
105         background-color: #da4f49;
106         border: 1px solid #bd362f;
107     }
108     .btn-danger:hover {
109         color: #fff;
110         background-color: #bd362f;
111     }
112     .btn:focus {
113         outline: none;
114     }
115     .todo-container {
116         width: 600px;
117         margin: 0 auto;
118     }
119     .todo-container .todo-wrap {
120         padding: 10px;
121         border: 1px solid #ddd;
122         border-radius: 5px;
123     }
124 </style>

MyHeader.vue

 1 <template>
 2     <div class="todo-header">
 3         <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add">
 4     </div>
 5 </template>
 6 <script>
 7     import {nanoid} from 'nanoid'
 8     export default {
 9         name:'MyHeader',
10         props:['addTodo'],
11         // data() {
12         //     return {
13         //         //收集用户输入的title
14         //         title:''
15         //     }
16         // },
17         methods: {
18             add(){
19                 //校验数据
20                 if(!this.title.trim()) return alert('输入不能为空')
21                 //将用户的输入包装成一个todo对象
22                 const todoObj = {id:nanoid(),title:this.title,done:false}
23                 this.addTodo(todoObj)
24                 //通知App组件去添加一个todo对象
25                 this.$emit('addTodo',todoObj,1,2,3)
26                 // this.addTodo(todoObj)
27                 //清空输入
28                 this.title = ''
29             }
30         },
31 }
32 </script>
33 <style scoped>
34     /*header*/
35     .todo-header input {
36         width: 560px;
37         height: 28px;
38         font-size: 14px;
39         border: 1px solid #ccc;
40         border-radius: 4px;
41         padding: 4px 7px;
42     }
43 
44     .todo-header input:focus {
45         outline: none;
46         border-color: rgba(82, 168, 236, 0.8);
47         box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
48     }
49 </style>

MyList.vue

 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"
 9         />
10       <!-- <MyItem></MyItem>
11       <MyItem></MyItem>
12       <MyItem></MyItem>
13       <MyItem></MyItem> -->
14     </ul>
15 </template>
16 <script>
17   import MyItem from './MyItem'
18 
19     export default {
20         name:'MyList',
21         components:{MyItem},
22         
23         // //声明接收App传递过来的数据,其中todos是自己用的,checkTodo和deleteTodo是给子组件MyItem用的
24         props:['todos','checkTodo','deleteTodo']
25 
26     }
27 </script>
28 <style scoped>
29    /* main */
30     .todo-main {
31         margin-left: 0px;
32         border: 1px solid #ddd;
33         border-radius: 2px;
34         padding: 0px;
35     }
36 
37     .todo-empty {
38         height: 40px;
39         line-height: 40px;
40         border: 1px solid #ddd;
41         border-radius: 2px;
42         padding-left: 5px;
43         margin-top: 10px;
44     }
45 </style>

MyItem.vue

 1 <template>
 2     <li>
 3         <label>
 4             <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
 5             <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props -->
 6             <!-- <input type="checkbox" v-model="todo.done"/> -->
 7             <span>{{todo.title}}</span>
 8         </label>
 9         <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
10     </li>
11 </template>
12 <script>
13     export default {
14         name:'MyItem',
15         //声明接收todo对象
16         props:['todo','checkTodo','deleteTodo'],
17         methods: {
18             //勾选or取消勾选
19             handleCheck(id){
20                 //通知App组件将对应的todo对象的done值取反
21                 this.checkTodo(id)
22                 // this.$bus.$emit('checkTodo',id)
23             },
24             //删除
25             handleDelete(id){
26                 if(confirm('确定删除吗?')){
27                     //通知App组件将对应的todo对象删除
28                     this.deleteTodo(id)
29                     // this.$bus.$emit('deleteTodo',id)
30                 }
31             }
32         },
33 }
34 </script>
35 <style scoped>
36     /*item*/
37     li {
38         list-style: none;
39         height: 36px;
40         line-height: 36px;
41         padding: 0 5px;
42         border-bottom: 1px solid #ddd;
43     }
44 
45     li label {
46         float: left;
47         cursor: pointer;
48     }
49 
50     li label li input {
51         vertical-align: middle;
52         margin-right: 6px;
53         position: relative;
54         top: -1px;
55     }
56 
57     li button {
58         float: right;
59         display: none;
60         margin-top: 3px;
61     }
62 
63     li:before {
64         content: initial;
65     }
66 
67     li:last-child {
68         border-bottom: none;
69     }
70 
71     li:hover{
72         background-color: #ddd;
73     }
74     
75     li:hover button{
76         display: block;
77     }
78 </style>

MyFooter.vue

 1 <template>
 2     <div class="todo-footer" v-show="total">
 3         <label>
 4             <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
 5             <input type="checkbox" v-model="isAll"/>
 6         </label>
 7         <span>
 8             <span>已完成{{doneTotal}}</span> / 全部{{total}}
 9         </span>
10         <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
11     </div>
12 </template>
13 <script>
14     export default {
15         name:'MyFooter',
16         props:['todos','checkAllTodo','clearAllTodo'],
17         computed: {
18             //总数
19             total(){
20                 return this.todos.length
21             },
22             //已完成数
23             doneTotal(){
24                 //此处使用reduce方法做条件统计
25                 /* const x = this.todos.reduce((pre,current)=>{
26                     console.log('@',pre,current)
27                     return pre + (current.done ? 1 : 0)
28                 },0) */
29                 //简写
30                 return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
31             },
32             //控制全选框
33             isAll:{
34                 //全选框是否勾选
35                 get(){
36                     return this.doneTotal === this.total && this.total > 0
37                 },
38                 //isAll被修改时set被调用
39                 set(value){
40                     this.checkAllTodo(value)
41                     // this.$emit('checkAllTodo',value)
42                 }
43             }
44         },
45         methods: {
46             /* checkAll(e){
47                 this.checkAllTodo(e.target.checked)
48             }, */
49             //清空所有已完成
50             clearAll(){
51                 // this.clearAllTodo()
52                 this.$emit('clearAllTodo')
53             }
54         },
55 }
56 </script>
57 <style scoped>
58     /*footer*/
59     .todo-footer {
60         height: 40px;
61         line-height: 40px;
62         padding-left: 6px;
63         margin-top: 5px;
64     }
65 
66     .todo-footer label {
67         display: inline-block;
68         margin-right: 20px;
69         cursor: pointer;
70     }
71 
72     .todo-footer label input {
73         position: relative;
74         top: -1px;
75         vertical-align: middle;
76         margin-right: 5px;
77     }
78 
79     .todo-footer button {
80         float: right;
81         margin-top: 5px;
82     }
83 </style>

 

组件化编码流程:

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

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

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

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

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

props适用于:

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

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

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

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

2.3 浏览器本地存储

LocalStorage

LocalStorage是HTML5新引入的特性,由于有的时候我们存储的信息较大,Cookie就不能满足我们的需求,这时候LocalStorage就派上用场了。

LocalStorage的优点:

  1. 在大小方面,LocalStorage的大小一般为5MB,可以储存更多的信息
  2. LocalStorage是持久储存,并不会随着页面的关闭而消失,除非主动清理,不然会永久存在
  3. 仅储存在本地,不像Cookie那样每次HTTP请求都会被携带

LocalStorage的缺点:

  1. 存在浏览器兼容问题,IE8以下版本的浏览器不支持
  2. 如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage
  3. LocalStorage受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问

LocalStorage的常用API:

 1 // 保存数据到 localStorage
 2 localStorage.setItem('key', 'value');
 3 
 4 // 从 localStorage 获取数据
 5 let data = localStorage.getItem('key');
 6 
 7 // 从 localStorage 删除保存的数据
 8 localStorage.removeItem('key');
 9 
10 // 从 localStorage 删除所有保存的数据
11 localStorage.clear();
12 
13 // 获取某个索引的Key
14 localStorage.key(index)

LocalStorage的使用场景:

  • 有些网站有换肤的功能,这时候就可以将换肤的信息存储在本地的LocalStorage中,当需要换肤的时候,直接操作LocalStorage即可
  • 在网站中的用户浏览信息也会存储在LocalStorage中,还有网站的一些不常变动的个人信息等也可以存储在本地的LocalStorage中

SessionStorage

SessionStorage和LocalStorage都是在HTML5才提出来的存储方案,SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。

SessionStorage与LocalStorage对比:

  • SessionStorage和LocalStorage都在本地进行数据存储;
  • SessionStorage也有同源策略的限制,但是SessionStorage有一条更加严格的限制,SessionStorage只有在同一浏览器的同一窗口下才能够共享;
  • LocalStorage和SessionStorage都不能被爬虫爬取;

SessionStorage的常用API:

 1 // 保存数据到 sessionStorage
 2 sessionStorage.setItem('key', 'value');
 3 
 4 // 从 sessionStorage 获取数据
 5 let data = sessionStorage.getItem('key');
 6 
 7 // 从 sessionStorage 删除保存的数据
 8 sessionStorage.removeItem('key');
 9 
10 // 从 sessionStorage 删除所有保存的数据
11 sessionStorage.clear();
12 
13 // 获取某个索引的Key
14 sessionStorage.key(index)

SessionStorage的使用场景

由于SessionStorage具有时效性,所以可以用来存储一些网站的游客登录的信息,还有临时的浏览记录的信息。当关闭网站之后,这些信息也就随之消除了。

案例:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>localStorage</title>
 8  
 9 </head>
10 <body>
11     <h2>localStorage</h2>
12     <button onclick="saveData()">点击保存数据</button>
13     <button onclick="readData()">点击读取数据</button>
14     <button onclick="deleteData()">点击删除数据</button>
15     <button onclick="deleteAllData()">点击清空数据</button>
16 
17     <script>
18         let p={name:'张三',age:18}
19         function saveData(){
20             localStorage.setItem('msg','hello!')
21             localStorage.setItem("msg2",666)
22             localStorage.setItem("person",JSON.stringify(p))
23         }
24         function readData(){
25          console.log(localStorage.getItem('msg'))
26          console.log(localStorage.getItem('msg2'))
27 
28          const result=localStorage.getItem('person')
29          console.log(JSON.parse(result))//解析result
30         }
31         function deleteData(){
32            localStorage.removeItem('msg2')
33         }
34         function deleteAllData(){
35             localStorage.clear()
36         }
37     </script>
38 </body>
39 </html>
 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>sessionStorage</title>
 8  
 9 </head>
10 <body>
11     <h2>sessionStorage</h2>
12     <button onclick="saveData()">点击保存数据</button>
13     <button onclick="readData()">点击读取数据</button>
14     <button onclick="deleteData()">点击删除数据</button>
15     <button onclick="deleteAllData()">点击清空数据</button>
16 
17     <script>
18         let p={name:'张三',age:18}
19         function saveData(){
20             sessionStorage.setItem('msg','hello!')
21             sessionStorage.setItem("msg2",666)
22             sessionStorage.setItem("person",JSON.stringify(p))
23         }
24         function readData(){
25          console.log(sessionStorage.getItem('msg'))
26          console.log(sessionStorage.getItem('msg2'))
27 
28          const result=sessionStorage.getItem('person')
29          console.log(JSON.parse(result))//解析result
30         }
31         function deleteData(){
32            sessionStorage.removeItem('msg2')
33         }
34         function deleteAllData(){
35             sessionStorage.clear()
36         }
37     </script>
38 </body>
39 </html>

 

TodoList本地存储

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" />
  7                 <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
  8             </div>
  9         </div>
 10     </div>
 11 </template>
 12 <script>
 13     import MyHeader from './components/MyHeader'
 14     import MyList from './components/MyList'
 15     import MyFooter from './components/MyFooter'
 16     export default {
 17         name:'App',
 18         // eslint-disable-next-line vue/no-unused-components
 19         components:{
 20             MyHeader,
 21             MyList,
 22             MyFooter,
 23             },
 24             data() {
 25             return {
 26                 //由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
 27                 todos:JSON.parse(localStorage.getItem('todos')) || []
 28             }
 29         },
 30             
 31         methods: {
 32             //添加一个todo
 33             addTodo(todoObj){
 34                 this.todos.unshift(todoObj)
 35             },
 36             //勾选or取消勾选一个todo
 37             checkTodo(id){
 38                 this.todos.forEach((todo)=>{
 39                     if(todo.id === id) todo.done = !todo.done
 40                 })
 41             },
 42             //删除一个todo
 43             deleteTodo(id){
 44                 this.todos = this.todos.filter( todo => todo.id !== id )
 45             },
 46             //全选or取消全选
 47             checkAllTodo(done){
 48                 this.todos.forEach((todo)=>{
 49                     todo.done = done
 50                 })
 51             },
 52             //清除所有已经完成的todo
 53             clearAllTodo(){
 54                 this.todos = this.todos.filter((todo)=>{
 55                     return !todo.done
 56                 })
 57             }
 58         },
 59         watch: {
 60             todos:{
 61                 deep:true,
 62                 handler(value){
 63                     localStorage.setItem('todos',JSON.stringify(value))//localStorage存储
 64                 }
 65             }
 66         },
 67         mounted() {
 68             this.$bus.$on('checkTodo',this.checkTodo)
 69             this.$bus.$on('deleteTodo',this.deleteTodo)
 70         },
 71         beforeDestroy() {
 72             this.$bus.$off('checkTodo')
 73             this.$bus.$off('deleteTodo')
 74         },
 75     }
 76 </script>
 77 
 78 <style>
 79     /*base*/
 80     body {
 81         background: #fff;
 82     }
 83     .btn {
 84         display: inline-block;
 85         padding: 4px 12px;
 86         margin-bottom: 0;
 87         font-size: 14px;
 88         line-height: 20px;
 89         text-align: center;
 90         vertical-align: middle;
 91         cursor: pointer;
 92         box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
 93         border-radius: 4px;
 94     }
 95     .btn-danger {
 96         color: #fff;
 97         background-color: #da4f49;
 98         border: 1px solid #bd362f;
 99     }
100     .btn-danger:hover {
101         color: #fff;
102         background-color: #bd362f;
103     }
104     .btn:focus {
105         outline: none;
106     }
107     .todo-container {
108         width: 600px;
109         margin: 0 auto;
110     }
111     .todo-container .todo-wrap {
112         padding: 10px;
113         border: 1px solid #ddd;
114         border-radius: 5px;
115     }
116 </style>

 

2.4 组件自定义事件

组件自定义事件是一种组件间通信的方式,适用于:子组件 ===> 父组件

使用场景

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

绑定自定义事件:

  • 第一种方式,在父组件中:<Demo @atguigu="test"/>或 <Demo v-on:atguigu="test"/>
  • 第二种方式,在父组件中:使用 this.$refs.xxx.$on() 这样写起来更灵活,比如可以加定时器啥的。

 

APP.vue

<template>
    <div class="app">
        <h1>{{msg}}</h1>
        <!-- 将方法交给子组件 -->
        <School :getSchoolName="getSchoolName"></School>

        <!-- v-on:绑定事件 简写@ -->
        <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递(第一种写法:使用@) -->
        <!-- <Student @atjiema="getStudentName"></Student> -->

        <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递(第二种写法:使用ref) -->
        <Student ref="student"></Student>
    </div>
</template>
<script>
    import Student from './components/Student'
    import School from './components/School'
    export default {
        name:'App',
        data() {
            return {
                msg:'你好啊'
            }
        },
        components:{
            Student,
            School
        },
        methods: {
            //写一个方法给子组件调用,接收学校名
            getSchoolName(name){
                console.log('APP收到了学校名',name)
            },
            getStudentName(name,...params){
                console.log('APP收到了学生名',name,...params)
            },
        },
        mounted() {
           setTimeout(() => {
            // this.$refs.student.$on('atjiema',this.getStudentName)
            this.$refs.student.$once('atjiema',this.getStudentName)//只触发一次
           }, 3000);
        },
    }
</script>
<style>
    .app{
        background-color: aquamarine;
        padding: 5px;
    }
</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     // eslint-disable-next-line vue/multi-word-component-names
12     name: 'School',
13     //接收来自父组件的方法
14     props:['getSchoolName'],
15     data() {
16         return {
17             name:'杰马课堂',
18             address:'天堂'
19         };
20     },
21     methods: {
22         //调用父组件的方法传递学校名
23         sendSchoolName(){
24             this.getSchoolName(this.name)
25         }
26     },
27     
28 };
29 </script>
30 <style scoped>
31     .school{
32         background-color: aqua;
33         padding: 5px
34     }
35 </style>

Student.vue

 1 <template>
 2     <div class="student">
 3         <h2>学生姓名:{{name}}</h2>
 4         <h2>学生性别:{{sex}}</h2>
 5         <button @click="sendStudentName">把学生名给APP</button>
 6     </div>
 7 </template>
 8 
 9 <script>
10     export default {
11     // eslint-disable-next-line vue/multi-word-component-names
12     name: 'Student',
13 
14     data() {
15         return {
16             name:'张三',
17             sex:''
18         };
19     },
20     methods: {
21         sendStudentName(){
22             //触发Student组件实例身上的atjiema事件
23             this.$emit('atjiema',this.name,666,888,999);//this.$emit:触发vc下指定事件
24         }
25     },
26 };
27 </script>
28 <style>
29     .student{
30         background-color: brown;
31         padding: 5px;
32         margin-top: 30px
33     }
34 </style>

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

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

使用 this.$emit() 就可以子组件向父组件传数据

 

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

 1 this.$off('atguigu') //解绑一个自定义事件 2 // this.$off(['atguigu','demo']) //解绑多个自定义事件 3 // this.$off() //解绑所有的自定义事件 

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

1 <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
2 <Student ref="student" @click.native="show"/>

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

 

2.5 全局事件总线

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 安装全局事件总线:

    1 new Vue({
    2     ......
    3     beforeCreate() {
    4         Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    5     },
    6     ......
    7 }) 

     

  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

    1 methods(){
    2   demo(data){......}
    3 }
    4 ......
    5 mounted() {
    6   this.$bus.$on('xxxx',this.demo)
    7 }

      2.提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

 

School.vue

<template>
    <div class="school">
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
    </div>
</template>

<script>
    export default {
    // eslint-disable-next-line vue/multi-word-component-names
    name: 'School',
    //接收来自父组件的方法
    data() {
        return {
            name:'杰马课堂',
            address:'天堂'
        };
    },
    mounted() {
        console.log('School',this.x)
        this.$bus.$on('hello', (data)=> {
            console.log('我是School组件,收到了数据',data)
        });
    },
    beforeDestroy() {
        this.$bus.$off('hello')
    },
};
</script>
<style scoped>
    .school{
        background-color: aqua;
        padding: 5px
    }
</style>

Student.vue

 1 <template>
 2     <div class="student">
 3         <h2>学生姓名:{{name}}</h2>
 4         <h2>学生性别:{{sex}}</h2>
 5         <button @click="sendStudentName">把学生们给School组件</button>
 6     </div>
 7 </template>
 8 
 9 <script>
10     export default {
11     // eslint-disable-next-line vue/multi-word-component-names
12     name: 'Student',
13 
14     data() {
15         return {
16             name:'张三',
17             sex:''
18         };
19     },
20     methods: {
21         sendStudentName(){
22             this.$bus.$emit('hello',this.name)
23         }
24     },
25     mounted() {
26         // console.log('Student',this.x)
27     },
28 };
29 </script>
30 <style>
31     .student{
32         background-color: brown;
33         padding: 5px;
34         margin-top: 30px
35     }
36 </style>

main.js

 1 /* 
 2   该文件是整个项目的入口文件
 3 */
 4 //引入Vue
 5 import Vue from 'vue'
 6 //引入App组件,它是所有组件的父组件
 7 import App from './App.vue'
 8 //关闭vue的生产提示
 9 Vue.config.productionTip=false
10 
11 //创建vue实例对象————vm
12 new Vue({
13     el:'#root',
14     //将app组件放入容器中
15     render:h => h(App),
16 
17     //安装全局事件总线
18     beforeCreate() {
19       Vue.prototype.$bus=this
20     },
21 })

 

2.6 消息订阅与发布

1.一种组件间通信的方式,适用于任意组件间通信。

2.使用步骤:

    • 安装pubsub:npm i pubsub-js
    • 引入: import pubsub from 'pubsub-js'

3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

1 methods:{
2   demo(data){......}
3 }
4 ......
5 mounted() {
6   this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
7 }

4.提供数据:pubsub.publish('xxx',数据)

5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。

 

School.vue

 1 <template>
 2     <div class="school">
 3         <h2>学校名称:{{name}}</h2>
 4         <h2>学校地址:{{address}}</h2>
 5     </div>
 6 </template>
 7 
 8 <script>
 9     import pubsub from 'pubsub-js'
10     export default {
11     // eslint-disable-next-line vue/multi-word-component-names
12     name: 'School',
13     //接收来自父组件的方法
14     data() {
15         return {
16             name:'杰马课堂',
17             address:'天堂'
18         };
19     },
20     mounted() {
21         // console.log('School',this.x)
22         /* this.$bus.$on('hello', (data)=> {
23             console.log('我是School组件,收到了数据',data)
24         }); */
25         this.pubId= pubsub.subscribe('hello',function(msgName,data){
26             console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
27         })
28     },
29     beforeDestroy() {
30         pubsub.unsubscribe(this.pubId)
31     },
32 };
33 </script>
34 <style scoped>
35     .school{
36         background-color: aqua;
37         padding: 5px
38     }
39 </style>

student.vue

 1 <template>
 2     <div class="student">
 3         <h2>学生姓名:{{name}}</h2>
 4         <h2>学生性别:{{sex}}</h2>
 5         <button @click="sendStudentName">把学生们给School组件</button>
 6     </div>
 7 </template>
 8 
 9 <script>
10     import pubsub from 'pubsub-js'
11     export default {
12     // eslint-disable-next-line vue/multi-word-component-names
13     name: 'Student',
14 
15     data() {
16         return {
17             name:'张三',
18             sex:''
19         };
20     },
21     methods: {
22         sendStudentName(){
23             // this.$bus.$emit('hello',this.name)
24             pubsub.publish('hello',666)
25         }
26     },
27     
28 };
29 </script>
30 <style>
31     .student{
32         background-color: brown;
33         padding: 5px;
34         margin-top: 30px
35     }
36 </style>

 

2.7 nextTick

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

具体案例 1 this.$nextTick(function(){ 2 this.$refs.inputTitle.focus() 3 } 

 

2.8 Vue封装的过度与动画

  1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。

  2.  

  3. 写法:

    准备好样式:

      元素进入的样式:
      v-enter:进入的起点
      v-enter-active:进入过程中
      v-enter-to:进入的终点
      元素离开的样式:
      v-leave:离开的起点
      v-leave-active:离开过程中
      v-leave-to:离开的终点
      使用<transition>包裹要过渡的元素,并配置name属性:

    1 <transition name="hello">
    2     <h1 v-show="isShow">你好啊!</h1>
    3 </transition>

    备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

 1 <template>
 2     <div>
 3         <button @click="isShow=!isShow">显示/隐藏</button>
 4         <!-- appear一开始就有效果 -->
 5         <transition name="hello" appear>
 6             <h1 v-show="isShow">你好啊!</h1>
 7         </transition>
 8     </div>
 9 </template>
10 <script>
11 export default {
12     // eslint-disable-next-line vue/multi-word-component-names
13     name: 'Test',
14     data() {
15         return {
16             isShow: 'true'
17         }
18     },
19 }
20 </script>
21 <style scoped>
22 h1 {
23     background-color: gold
24 }
25 /* 如果给<transition name="hello">命名为hello,需要用.hello-enter-active*/
26 /* 如果没有命名,使用.v-enter-active */
27 .hello-enter-active {
28     animation: atjiema 0.5s linear;
29 }
30 
31 .hello-leave-active {
32     animation: atjiema 0.5s linear reverse;
33 }
34 
35 @keyframes atjiema {
36     from {
37         transform: translateX(-100%)
38     }
39     to {
40         transform: translateX(0px)
41     }
42 }
43 </style>
 1 <template>
 2     <div>
 3         <button @click="isShow=!isShow">显示/隐藏</button>
 4         <!-- appear一开始就有效果 -->
 5         <transition name="hello" appear>
 6             <h1 v-show="isShow">你好啊!</h1>
 7         </transition>
 8     </div>
 9 </template>
10 <script>
11 export default {
12     // eslint-disable-next-line vue/multi-word-component-names
13     name: 'Test2',
14     data() {
15         return {
16             isShow: 'true'
17         }
18     },
19 }
20 </script>
21 <style scoped>
22 h1 {
23     background-color: gold
24     
25 }
26 /* 进入的起点,离开的终点 */
27 .hello-enter,.hello-leave-to{
28     transform: translateX(-100%)
29 }
30 .hello-enter-active,.hello-leave-active{
31     transition: 0.5s linear
32 }
33 /* 进入的终点,离开的起点 */
34 .hello-enter-to,.hello-leave{
35     transform: translateX(0)
36 }
37 
38 
39 </style>
 1 <template>
 2     <div>
 3         <button @click="isShow=!isShow">显示/隐藏</button>
 4         <!-- appear一开始就有效果 -->
 5         <transition-group name="hello" appear>
 6             <h1 v-show="isShow" key="1">你好啊!</h1>
 7             <h1 v-show="isShow" key="2">杰马集团</h1>
 8         </transition-group>
 9     </div>
10 </template>
11 <script>
12 export default {
13     // eslint-disable-next-line vue/multi-word-component-names
14     name: 'Test3',
15     data() {
16         return {
17             isShow: 'true'
18         }
19     },
20 }
21 </script>
22 <style scoped>
23 h1 {
24     background-color: gold
25     
26 }
27 /* 进入的起点,离开的终点 */
28 .hello-enter,.hello-leave-to{
29     transform: translateX(-100%)
30 }
31 .hello-enter-active,.hello-leave-active{
32     transition: 0.5s linear
33 }
34 /* 进入的终点,离开的起点 */
35 .hello-enter-to,.hello-leave{
36     transform: translateX(0)
37 }
38 
39 
40 </style>

使用第三库的具体案例(随便看看,这个不重要)
库的名称:Animate.css
安装:npm i animate.css
引入:import ‘animate.css

 1 <template>
 2     <div>
 3         <button @click="isShow=!isShow">显示/隐藏</button>
 4         <!-- appear一开始就有效果 -->
 5         <transition-group 
 6             appear 
 7             name="animate__animated animate__bounce"
 8             enter-active-class="animate__swing"
 9             leave-active-class="animate__rotateOutDownLeft"
10             >
11             <h1 v-show="isShow" key="2">杰马集团</h1>
12         </transition-group>
13     </div>
14 </template>
15 <script>
16 import 'animate.css'
17 export default {
18     // eslint-disable-next-line vue/multi-word-component-names
19     name: 'Test4',
20     data() {
21         return {
22             isShow: 'true'
23         }
24     },
25 }
26 </script>
27 <style scoped>
28 h1 {
29     background-color: gold
30     
31 }
32 
33 
34 </style>

2.9 vue脚手架配置代理

可以用来解决跨域的问题

ajax 是前端技术,你得有浏览器,才有window对象,才有xhr,才能发ajax请求,服务器之间通信就用传统的http请求就行了。

方法一

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

1 devServer:{
2   proxy:"http://localhost:5000"
3 }

App.vue

 1 <template>
 2     <div>
 3         <button @click="getStudents">获取学生信息</button>
 4     </div>
 5 </template>
 6 
 7 <script>
 8     import axios from 'axios'
 9     export default {
10         name:'App',
11         methods: {
12             getStudents(){
13                 axios.get('http://localhost:8080/students').then(
14                     response=>{
15                         console.log('请求成功了',response.data)
16                     },
17                     error=>{
18                         console.log('请求失败了',error.message)
19                     }
20                 )
21             }
22         },
23         
24     }        
25 </script>

说明:

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

方法二

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

 1 module.exports = {
 2     devServer: {
 3       proxy: {
 4       '/api1': {// 匹配所有以 '/api1'开头的请求路径
 5         target: 'http://localhost:5000',// 代理目标的基础路径
 6         changeOrigin: true,
 7         pathRewrite: {'^/api1': ''}//代理服务器将请求地址转给真实服务器时会将 /api1 去掉
 8       },
 9       '/api2': {// 匹配所有以 '/api2'开头的请求路径
10         target: 'http://localhost:5001',// 代理目标的基础路径
11         changeOrigin: true,
12         pathRewrite: {'^/api2': ''}
13       }
14     }
15   }
16 }
17 /*
18    changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
19    changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
20    changeOrigin默认值为true
21 */

App.vue

 1 <template>
 2     <div>
 3         <button @click="getStudents">获取学生信息</button>
 4     </div>
 5 </template>
 6 
 7 <script>
 8     import axios from 'axios'
 9     export default {
10         name:'App',
11         methods: {
12             getStudents(){
13                 axios.get('http://localhost:8080/api/students').then(
14                     response=>{
15                         console.log('请求成功了',response.data)
16                     },
17                     error=>{
18                         console.log('请求失败了',error.message)
19                     }
20                 )
21             }
22         },
23         
24     }        
25 </script>

 

说明:

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

 2.10 github搜索案例

List.vue

 1 <template>
 2     <div class="row">
 3         <!-- 展示用户列表 -->
 4         <div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login">
 5             <a :href="user.html_url" target="_blank">
 6                 <img :src="user.avatar_url" style='width: 100px'/>
 7             </a>
 8             <p class="card-text">{{user.login}}</p>
 9         </div>
10         <!-- 展示欢迎词 -->
11         <h1 v-show="info.isFirst">Welcome to use!</h1>
12         <!-- 展示加载中 -->
13         <h1 v-show="info.isLoading">loading...</h1>
14         <!-- 展示错误信息 -->
15         <h1 v-show="info.errMsg">{{info.errMsg}}</h1>
16     </div>
17 </template>
18 
19 <script>
20     export default {
21         // eslint-disable-next-line vue/multi-word-component-names
22         name:'List',
23         data() {
24             return {
25                 info:{
26                     isFirst:true,
27                     isLoading:false,
28                     errMsg:'',
29                     users:[]
30                 }
31             }
32         },
33         mounted() {
34             this.$bus.$on('updateListData',(dataObj)=>{
35                 this.info = {...this.info,...dataObj}
36             })
37         },
38     }
39 </script>
40 
41 <style scoped>
42     .album {
43         min-height: 50rem; /* Can be removed; just added for demo purposes */
44         padding-top: 3rem;
45         padding-bottom: 3rem;
46         background-color: #f7f7f7;
47     }
48 
49     .card {
50         float: left;
51         width: 33.333%;
52         padding: .75rem;
53         margin-bottom: 2rem;
54         border: 1px solid #efefef;
55         text-align: center;
56     }
57 
58     .card > img {
59         margin-bottom: .75rem;
60         border-radius: 100px;
61     }
62 
63     .card-text {
64         font-size: 85%;
65     }
66 </style>

Search.vue

 1 <template>
 2     <section class="jumbotron">
 3         <h3 class="jumbotron-heading">Search Github Users</h3>
 4         <div>
 5             <input type="text" placeholder="enter the name you search" v-model="keyWord"/>&nbsp;
 6             <button @click="searchUsers">Search</button>
 7         </div>
 8     </section>
 9 </template>
10 
11 <script>
12     import axios from 'axios'
13     export default {
14         // eslint-disable-next-line vue/multi-word-component-names
15         name:'Search',
16         data() {
17             return {
18                 keyWord:''
19             }
20         },
21         methods: {
22             searchUsers(){
23                 //请求前更新List的数据
24                 this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false})
25                 axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
26                     response => {
27                         console.log('请求成功了')
28                         //请求成功后更新List的数据
29                         this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items})
30                     },
31                     error => {
32                         //请求失败后更新List的数据
33                         this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]})
34                     }
35                 )
36             }
37         },
38     }
39 </script>

App.vue

 1 <template>
 2   <div class="container">
 3     <Search></Search>
 4     <List></List>
 5   </div>
 6 
 7 </template>
 8 
 9 <script>
10 import List from "./components/List";
11 import Search from "./components/Search";
12 export default {
13   components: {
14     List,
15     Search,
16   },
17   name: "App",
18 };
19 </script>
20 
21 <style>
22 </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:'#root',
11     render: h => h(App),
12     beforeCreate() {
13         Vue.prototype.$bus = this
14     },
15 })

 

2.11 slot插槽

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

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

  3. 使用方式:

    1. 默认插槽

Category.vue:

 1 <template>
 2     <div class="category">
 3         <h3>{{title}}分类</h3>
 4         <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
 5         <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
 6     </div>
 7 </template>
 8 
 9 <script>
10     export default {
11         // eslint-disable-next-line vue/multi-word-component-names
12         name:'Category',
13         props:['title']
14     }
15 </script>
16 
17 <style scoped>
18     .category{
19         background-color: skyblue;
20         width: 200px;
21         height: 300px;
22     }
23     h3{
24         text-align: center;
25         background-color: orange;
26     }
27     video{
28         width: 100%;
29     }
30     img{
31         width: 100%;
32     }
33 </style>

App.vue:

 1 <template>
 2     <div class="container">
 3         <Category title="美食" >
 4             <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
 5         </Category>
 6 
 7         <Category title="游戏" >
 8             <ul>
 9                 <li v-for="(g,index) in games" :key="index">{{g}}</li>
10             </ul>
11         </Category>
12 
13         <Category title="电影">
14             <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
15         </Category>
16     </div>
17 </template>
18 
19 <script>
20     import Category from './components/Category'
21     export default {
22         name:'App',
23         components:{Category},
24         data() {
25             return {
26                 foods:['火锅','烧烤','小龙虾','牛排'],
27                 games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
28                 films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
29             }
30         },
31     }
32 </script>
33 
34 <style scoped>
35     .container{
36         display: flex;
37         justify-content: space-around;
38     }
39 </style>

 

    2.具名插槽:

Category.vue:

 1 <template>
 2     <div class="category">
 3         <h3>{{title}}分类</h3>
 4         <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
 5         <slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
 6         <slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
 7     </div>
 8 </template>
 9 
10 <script>
11     export default {
12         // eslint-disable-next-line vue/multi-word-component-names
13         name:'Category',
14         props:['title']
15     }
16 </script>
17 
18 <style scoped>
19     .category{
20         background-color: skyblue;
21         width: 200px;
22         height: 300px;
23     }
24     h3{
25         text-align: center;
26         background-color: orange;
27     }
28     video{
29         width: 100%;
30     }
31     img{
32         width: 100%;
33     }
34 </style>

App.vue:

 1 <template>
 2     <div class="container">
 3         <Category title="美食" >
 4             <img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
 5       <a slot="footer" href="http://www.baidu.com">更多美食</a>
 6         </Category>
 7         <Category title="游戏" >
 8             <ul slot="center">
 9                 <li v-for="(g,index) in games" :key="index">{{g}}</li>
10             </ul>
11             <div class="foot" slot="footer">
12                 <a href="http://www.baidu.com">单机游戏</a>
13                 <a href="http://www.baidu.com">网络游戏</a>
14             </div>
15         </Category>
16 
17         <Category title="电影">
18             <video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
19             <template v-slot:footer>
20                 <div class="foot">
21                     <a href="http://www.baidu.com">经典</a>
22                     <a href="http://www.baidu.com">热门</a>
23                     <a href="http://www.baidu.com">推荐</a>
24                 </div>
25                 <h4>欢迎前来观影</h4>
26             </template>
27         </Category>
28     </div>
29 </template>
30 
31 <script>
32     import Category from './components/Category'
33     export default {
34         name:'App',
35         components:{Category},
36         data() {
37             return {
38                 foods:['火锅','烧烤','小龙虾','牛排'],
39                 games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
40                 films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
41             }
42         },
43     }
44 </script>
45 
46 <style scoped>
47     .container{
48         display: flex;
49         justify-content: space-around;
50     }
51     .foot{
52         display: flex;
53         justify-content: space-around;
54     }
55     h4{
56         display: flex;
57         justify-content: space-around;
58     }
59 
60 </style>

 

    3.作用域插槽:

Category.vue:

 1 <template>
 2     <div class="category">
 3         <h3>{{title}}分类</h3>
 4         <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
 5         <slot :games="games">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
 6         
 7     </div>
 8 </template>
 9 
10 <script>
11     export default {
12         // eslint-disable-next-line vue/multi-word-component-names
13         name:'Category',
14         props:['title'],
15         data() {
16             return {
17                 games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
18             }
19         },
20     }
21 </script>
22 
23 <style scoped>
24     .category{
25         background-color: skyblue;
26         width: 200px;
27         height: 300px;
28     }
29     h3{
30         text-align: center;
31         background-color: orange;
32     }
33     video{
34         width: 100%;
35     }
36     img{
37         width: 100%;
38     }
39 </style>

App.vue:

 1 <template>
 2     <div class="container">
 3 
 4         <Category title="游戏" >
 5             <template scope={games}>
 6             <ul>
 7                 <li v-for="(g,index) in games" :key="index">{{g}}</li>
 8             </ul>
 9             </template>
10         </Category>
11 
12         <Category title="游戏" >
13             <template scope={games}>
14             <ol>
15                 <li v-for="(g,index) in games" :key="index">{{g}}</li>
16             </ol>
17             </template>
18         </Category>
19 
20         <Category title="游戏" >
21             <template slot-scope={games}>
22                 <h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
23             </template>
24         </Category>
25 
26     </div>
27 </template>
28 
29 <script>
30     import Category from './components/Category'
31     export default {
32         name:'App',
33         components:{Category},
34         
35     }
36 </script>
37 
38 <style scoped>
39     .container{
40         display: flex;
41         justify-content: space-around;
42     }
43     .foot{
44         display: flex;
45         justify-content: space-around;
46     }
47     h4{
48         display: flex;
49         justify-content: space-around;
50     }
51 
52 </style>

 

3. VUEX

3.1 概念

​ 在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

3.2 何时使用?

​ 多个组件需要共享数据时

3.3 搭建vuex环境

  1.创建文件:src/store/index.js

 1 //该文件用于创建Vuex中最为核心的store
 2 
 3 //引入Vue
 4 import Vue from 'vue'
 5 //引入Vuex
 6 import Vuex from 'vuex'
 7 //使用Vuex
 8 Vue.use(Vuex)
 9 //准备actions-用于响应组件中的动作
10 const actions={}
11 //准备mutations-用于操作数据(state)
12 const mutations={}
13 //准备state-用于存储数据
14 const state={}
15 
16 //创建并暴露store
17 export default new Vuex.Store({
18     actions,
19     mutations,
20     state
21 })

  2.在main.js中创建vm时传入store配置项

 1 //引入Vue
 2 import Vue from 'vue'
 3 //引入App
 4 import App from './App.vue'
 5 //引入插件
 6 import vueResource from 'vue-resource'
 7 //引入store
 8 import store from './store/index'
 9 //关闭Vue的生产提示
10 Vue.config.productionTip = false
11 //使用插件
12 Vue.use(vueResource)
13 
14 //创建vm
15 new Vue({
16     el:'#root',
17     render: h => h(App),
18     store,
19     beforeCreate() {
20         Vue.prototype.$bus = this
21     },
22 })

3.4 基本使用

  1.初始化数据、配置actions、配置mutations,操作文件store.js

 1 //引入Vue核心库
 2 import Vue from 'vue'
 3 //引入Vuex
 4 import Vuex from 'vuex'
 5 //引用Vuex
 6 Vue.use(Vuex)
 7 
 8 const actions = {
 9     //响应组件中加的动作
10     jia(context,value){
11         // console.log('actions中的jia被调用了',miniStore,value)
12         context.commit('JIA',value)
13     },
14 }
15 
16 const mutations = {
17     //执行加
18     JIA(state,value){
19         // console.log('mutations中的JIA被调用了',state,value)
20         state.sum += value
21     }
22 }
23 
24 //初始化数据
25 const state = {
26    sum:0
27 }
28 
29 //创建并暴露store
30 export default new Vuex.Store({
31     actions,
32     mutations,
33     state,
34 })

  2.组件中读取vuex中的数据:$store.state.sum

  3.组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)或 $store.commit('mutations中的方法名',数据)

  备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

简单案例:

纯vue版:

  Count.vue

 1 <template>
 2     <div>
 3         <h1>当前求和为:{{sum}}</h1>
 4         <select v-model.number="n">
 5             <option value="1">1</option>
 6             <option value="2">2</option>
 7             <option value="3">3</option>
 8         </select>
 9         <button @click="increment">+</button>
10         <button @click="decrement">-</button>
11         <button @click="incrementOdd">当前求和为奇数再加</button>
12         <button @click="incrementWait">等一等再加</button>
13     </div>
14 </template>
15 
16 <script>
17     export default {
18         name:'Count',
19         data() {
20             return {
21                 n:1, //用户选择的数字
22                 sum:0 //当前的和
23             }
24         },
25         methods: {
26             increment(){
27                 this.sum += this.n
28             },
29             decrement(){
30                 this.sum -= this.n
31             },
32             incrementOdd(){
33                 if(this.sum % 2){
34                     this.sum += this.n
35                 }
36             },
37             incrementWait(){
38                 setTimeout(()=>{
39                     this.sum += this.n
40                 },500)
41             },
42         },
43     }
44 </script>
45 
46 <style lang="css">
47     button{
48         margin-left: 5px;
49     }
50 </style>

  App.vue

 1 <template>
 2     <div>
 3         <Count/>
 4     </div>
 5 </template>
 6 
 7 <script>
 8     import Count from './components/Count'
 9     export default {
10         name:'App',
11         components:{Count},
12     }
13 </script>

  main.js

 1 //引入Vue
 2 import Vue from 'vue'
 3 //引入App
 4 import App from './App.vue'
 5 //引入插件
 6 import vueResource from 'vue-resource'
 7 //关闭Vue的生产提示
 8 Vue.config.productionTip = false
 9 //使用插件
10 Vue.use(vueResource)
11 
12 //创建vm
13 new Vue({
14     el:'#app',
15     render: h => h(App),
16     beforeCreate() {
17         Vue.prototype.$bus = this
18     }
19 })

Vuex版:

  Count.vue

 1 <template>
 2     <div>
 3         <h1>当前求和为:{{$store.state.sum}}</h1>
 4         <select v-model="n">
 5             <option :value="1">1</option>
 6             <option :value="2">2</option>
 7             <option :value="3">3</option>
 8         </select>
 9         <button @click="increment">+</button>
10         <button @click="decrement">-</button>
11         <button @click="incrementOdd">当前求和为奇数再加</button>
12         <button @click="incrementWait">等一等再加</button>
13     </div>
14 </template>
15 
16 <script>
17     export default {
18         // eslint-disable-next-line vue/multi-word-component-names
19         name:'Count',
20         data(){
21             return{
22                 n:1,//用户选择的数字
23             }
24         },
25         methods:{
26             //如果没有什么业务逻辑,可直接跳过action,直接调用commit
27             increment(){
28                 this.$store.commit('JIA',this.n)
29             },
30             decrement(){
31                 this.$store.commit('JIAN',this.n)
32             },
33             incrementOdd(){
34                this.$store.dispatch('oddJia',this.n)
35             },
36             incrementWait(){
37                 this.$store.dispatch('waitJia',this.n)
38             }
39         }
40     }
41 </script>
42 
43 <style>
44     button{
45         margin-left: 5px;
46     }
47 </style>

  App.vue

 1 <template>
 2     <div>
 3         <Count></Count>
 4     </div>
 5 </template>
 6 
 7 <script>
 8     import Count from './components/Count'
 9     export default {
10         name:'App',
11         components:{Count}
12     }
13 </script>
14 
15 <style>
16 
17 </style>

  index.js

 1 //该文件用于创建Vuex中最为核心的store
 2 
 3 //引入Vue
 4 import Vue from 'vue'
 5 //引入Vuex
 6 import Vuex from 'vuex'
 7 //使用Vuex
 8 Vue.use(Vuex)
 9 //准备actions-用于响应组件中的动作
10 const actions={
11     /* jia(context,value){
12         context.commit('JIA',value)
13     }, */
14     /* jian(context,value){
15         context.commit('JIAN',value)
16     }, */
17     oddJia(context,value){
18         if(context.state.sum % 2){
19             context.commit('ODDJIA',value)
20         }
21     },
22     waitJia(context,value){
23         setTimeout(() => {
24             context.commit('WAITJIA',value)
25         }, 500);
26     }
27 }
28 //准备mutations-用于操作数据(state)
29 const mutations={
30     JIA(state,value){
31         state.sum += value
32     },
33     JIAN(state,value){
34         state.sum -= value
35     },
36     ODDJIA(state,value){
37         state.sum += value
38     },
39     WAITJIA(state,value){
40         state.sum += value
41     }
42 }
43 //准备state-用于存储数据
44 const state={
45     sum:0,//当前的和
46 }
47 
48 //创建并暴露store
49 export default new Vuex.Store({
50     actions,
51     mutations,
52     state
53 })

3.5 getters的使用

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。

  2. store.js中追加getters配置

     1 ......
     2 
     3 const getters = {
     4     bigSum(state){
     5         return state.sum * 10
     6     }
     7 }
     8 
     9 //创建并暴露store
    10 export default new Vuex.Store({
    11     ......
    12     getters
    13 })

   3.组件中读取数据:$store.getters.bigSum

3.6 四个map方法的使用

导入:

 1 import {mapState, mapGetters, mapActions, mapMutations} from 'vuex' 

  1.mapState方法:用于帮助我们映射state中的数据为计算属性

1 computed: {
2     //借助mapState生成计算属性:sum、school、subject(对象写法)
3      ...mapState({sum:'sum',school:'school',subject:'subject'}),
4          
5     //借助mapState生成计算属性:sum、school、subject(数组写法)
6     ...mapState(['sum','school','subject']),
7 },

  2.mapGetters方法:用于帮助我们映射getters中的数据为计算属性

1 computed: {
2     //借助mapGetters生成计算属性:bigSum(对象写法)
3     ...mapGetters({bigSum:'bigSum'}),
4 
5     //借助mapGetters生成计算属性:bigSum(数组写法)
6     ...mapGetters(['bigSum'])
7 },

  3.mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

1 methods:{
2     //靠mapActions生成:incrementOdd、incrementWait(对象形式)
3     ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
4 
5     //靠mapActions生成:incrementOdd、incrementWait(数组形式)
6     ...mapActions(['jiaOdd','jiaWait'])
7 }

  4.mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

1 methods:{
2     //靠mapActions生成:increment、decrement(对象形式)
3     ...mapMutations({increment:'JIA',decrement:'JIAN'}),
4     
5     //靠mapMutations生成:JIA、JIAN(对象形式)
6     ...mapMutations(['JIA','JIAN']),
7 }

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则传的参数是事件对象(event)。

具体案例:

  App.vue

 1 <template>
 2     <div>
 3         <Count></Count>
 4     </div>
 5 </template>
 6 
 7 <script>
 8     import Count from './components/Count'
 9     export default {
10         name:'App',
11         components:{Count}
12     }
13 </script>
14 
15 <style>
16 
17 </style>

  main.js

 1 //引入Vue
 2 import Vue from 'vue'
 3 //引入App
 4 import App from './App.vue'
 5 //引入插件
 6 import vueResource from 'vue-resource'
 7 //引入store
 8 import store from './store/index'
 9 //关闭Vue的生产提示
10 Vue.config.productionTip = false
11 //使用插件
12 Vue.use(vueResource)
13 
14 //创建vm
15 new Vue({
16     el:'#root',
17     render: h => h(App),
18     store,
19     beforeCreate() {
20         Vue.prototype.$bus = this
21     },
22 })

  index.js

 1 //该文件用于创建Vuex中最为核心的store
 2 
 3 //引入Vue
 4 import Vue from 'vue'
 5 //引入Vuex
 6 import Vuex from 'vuex'
 7 //使用Vuex
 8 Vue.use(Vuex)
 9 //准备actions-用于响应组件中的动作
10 const actions={
11     /* jia(context,value){
12         context.commit('JIA',value)
13     }, */
14     /* jian(context,value){
15         context.commit('JIAN',value)
16     }, */
17     oddJia(context,value){
18         if(context.state.sum % 2){
19             context.commit('ODDJIA',value)
20         }
21     },
22     waitJia(context,value){
23         setTimeout(() => {
24             context.commit('WAITJIA',value)
25         }, 500);
26     }
27 }
28 //准备mutations-用于操作数据(state)
29 const mutations={
30     JIA(state,value){
31         state.sum += value
32     },
33     JIAN(state,value){
34         state.sum -= value
35     },
36     ODDJIA(state,value){
37         state.sum += value
38     },
39     WAITJIA(state,value){
40         state.sum += value
41     }
42 }
43 //准备state-用于存储数据
44 const state={
45     sum:0,//当前的和
46     school:'杰马课堂',
47     subject:'前端'
48 }
49 //准备getters-用于将state中的数据进行加工
50 const getters={
51     bigSum(state){
52         return state.sum*10
53 }
54     }
55 //创建并暴露store
56 export default new Vuex.Store({
57     actions,
58     mutations,
59     state,
60     getters
61 })

  mapState与mapGetters:Count.vue

 1 <template>
 2     <div>
 3         <h1>当前求和为:{{sum}}</h1>
 4         <h3>当前求和放大10倍为:{{bigSum}}</h3>
 5         <h3>我在{{school}},学习{{subject}}</h3>
 6         <select v-model="n">
 7             <option :value="1">1</option>
 8             <option :value="2">2</option>
 9             <option :value="3">3</option>
10         </select>
11         <button @click="increment">+</button>
12         <button @click="decrement">-</button>
13         <button @click="incrementOdd">当前求和为奇数再加</button>
14         <button @click="incrementWait">等一等再加</button>
15     </div>
16 </template>
17 
18 <script>
19     import { mapState,mapGetters } from 'vuex'
20     export default {
21         // eslint-disable-next-line vue/multi-word-component-names
22         name:'Count',
23         data(){
24             return{
25                 n:1,//用户选择的数字
26             }
27         },
28         computed:{
29             //靠程序员自己写计算属性
30            /*  sum(){
31                 return this.$store.state.sum
32             },
33             school(){
34                 return this.$store.state.school
35             },
36             subject(){
37                 return this.$store.state.subject
38             }, */
39 
40             /* //...:把对象依次取出放入
41             //借助mapState生成计算属性,从state中读取数据(对象写法)
42             ...mapState({he:'sum',xuexiao:'school',xueke:'subject'}), */
43             //借助mapState生成计算属性,从state中读取数据(数组写法)
44             ...mapState(['sum','school','subject']),
45              /* bigSum(){
46                 return this.$store.getters.bigSum
47             }, */
48             ...mapGetters(['bigSum'])
49         },
50         methods:{
51             //如果没有什么业务逻辑,可直接跳过action,直接调用commit
52             increment(){
53                 this.$store.commit('JIA',this.n)
54             },
55             decrement(){
56                 this.$store.commit('JIAN',this.n)
57             },
58             incrementOdd(){
59                this.$store.dispatch('oddJia',this.n)
60             },
61             incrementWait(){
62                 this.$store.dispatch('waitJia',this.n)
63             }
64         }
65     }
66 </script>
67 
68 <style>
69     button{
70         margin-left: 5px;
71     }
72 </style>
  mapMutations与mapActions:Count.vue
 1 <template>
 2     <div>
 3         <h1>当前求和为:{{sum}}</h1>
 4         <h3>当前求和放大10倍为:{{bigSum}}</h3>
 5         <h3>我在{{school}},学习{{subject}}</h3>
 6         <select v-model="n">
 7             <option :value="1">1</option>
 8             <option :value="2">2</option>
 9             <option :value="3">3</option>
10         </select>
11         <button @click="increment(n)">+</button>
12         <button @click="decrement(n)">-</button>
13         <button @click="incrementOdd(n)">当前求和为奇数再加</button>
14         <button @click="incrementWait(n)">等一等再加</button>
15     </div>
16 </template>
17 
18 <script>
19     import { mapState,mapGetters,mapMutations,mapActions } from 'vuex'
20     export default {
21         // eslint-disable-next-line vue/multi-word-component-names
22         name:'Count',
23         data(){
24             return{
25                 n:1,//用户选择的数字
26             }
27         },
28         computed:{
29             /* //...:把对象依次取出放入
30             //借助mapState生成计算属性,从state中读取数据(对象写法)
31             ...mapState({he:'sum',xuexiao:'school',xueke:'subject'}), */
32             //借助mapState生成计算属性,从state中读取数据(数组写法)
33             ...mapState(['sum','school','subject']),
34 
35             ...mapGetters(['bigSum'])
36         },
37         methods:{
38             //程序员亲自写方法
39             //如果没有什么业务逻辑,可直接跳过action,直接调用commit
40             /* increment(){
41                 this.$store.commit('JIA',this.n)
42             },
43             decrement(){
44                 this.$store.commit('JIAN',this.n)
45             }, */
46             //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations
47             ...mapMutations({increment:'JIA',decrement:'JIAN'}),
48 
49             //程序员亲自写方法
50            /*  incrementOdd(){
51                this.$store.dispatch('oddJia',this.n)
52             },
53             incrementWait(){
54                 this.$store.dispatch('waitJia',this.n)
55             }, */
56             //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions
57             ...mapActions({incrementOdd:'oddJia',incrementWait:'waitJia'})
58         }
59     }
60 </script>
61 
62 <style>
63     button{
64         margin-left: 5px;
65     }
66 </style>

多组件间共享数据

App.vue:

 1 <template>
 2     <div>
 3         <Count></Count>
 4         <hr>
 5         <Person></Person>
 6     </div>
 7 </template>
 8 
 9 <script>
10     import Count from './components/Count'
11     import Person from './components/Person'
12     export default {
13         name:'App',
14         components:{Count,Person}
15     }
16 </script>
17 
18 <style>
19 
20 </style>

Count.vue:

 1 <template>
 2     <div>
 3         <h1>当前求和为:{{sum}}</h1>
 4         <h3>当前求和放大10倍为:{{bigSum}}</h3>
 5         <h3>我在{{school}},学习{{subject}}</h3>
 6         <h3 style="color: blue;">下方列表的总人数是:{{personList.length}}</h3>
 7         <select v-model="n">
 8             <option :value="1">1</option>
 9             <option :value="2">2</option>
10             <option :value="3">3</option>
11         </select>
12         <button @click="increment(n)">+</button>
13         <button @click="decrement(n)">-</button>
14         <button @click="incrementOdd(n)">当前求和为奇数再加</button>
15         <button @click="incrementWait(n)">等一等再加</button>
16     </div>
17 </template>
18 
19 <script>
20     import { mapState,mapGetters,mapMutations,mapActions } from 'vuex'
21     export default {
22         // eslint-disable-next-line vue/multi-word-component-names
23         name:'Count',
24         data(){
25             return{
26                 n:1,//用户选择的数字
27             }
28         },
29         computed:{
30             ...mapState(['sum','school','subject','personList']),
31             ...mapGetters(['bigSum'])
32         },
33         methods:{
34             ...mapMutations({increment:'JIA',decrement:'JIAN'}),
35             ...mapActions({incrementOdd:'oddJia',incrementWait:'waitJia'})
36         }
37     }
38 </script>
39 
40 <style>
41     button{
42         margin-left: 5px;
43     }
44 </style>

Person.vue:

 1 <template>
 2     <div>
 3         <h1>人员列表</h1>
 4         <h3 style="color:blue">上方组件的求和为:{{sum}}</h3>
 5         <input type="text" placeholder="请输入名字" v-model="name">
 6         <button @click="add">添加</button>
 7         <ul>
 8             <li v-for="p in personList" :key="p.id">{{p.name}}</li>
 9         </ul>
10     </div>
11 </template>
12 
13 <script>
14     import { nanoid } from 'nanoid'
15     import { mapState } from 'vuex'
16     export default {
17         // eslint-disable-next-line vue/multi-word-component-names
18         name:'Person',
19         data(){
20             return{
21                 name:''
22             }
23         },
24         computed:{
25             ...mapState(['personList']),
26             ...mapState(['sum'])
27         },
28         methods:{
29             add(){
30                 const personObj={id:nanoid(),name:this.name}
31                 this.$store.commit('ADD_PERSON',personObj)
32                 this.name=''
33             }
34         }
35     }
36 </script>
37 
38 <style>
39 
40 </style>

index.js:

 1 //该文件用于创建Vuex中最为核心的store
 2 
 3 //引入Vue
 4 import Vue from 'vue'
 5 //引入Vuex
 6 import Vuex from 'vuex'
 7 //使用Vuex
 8 Vue.use(Vuex)
 9 //准备actions-用于响应组件中的动作
10 const actions={
11     /* jia(context,value){
12         context.commit('JIA',value)
13     }, */
14     /* jian(context,value){
15         context.commit('JIAN',value)
16     }, */
17     oddJia(context,value){
18         if(context.state.sum % 2){
19             context.commit('ODDJIA',value)
20         }
21     },
22     waitJia(context,value){
23         setTimeout(() => {
24             context.commit('WAITJIA',value)
25         }, 500);
26     }
27 }
28 //准备mutations-用于操作数据(state)
29 const mutations={
30     JIA(state,value){
31         state.sum += value
32     },
33     JIAN(state,value){
34         state.sum -= value
35     },
36     ODDJIA(state,value){
37         state.sum += value
38     },
39     WAITJIA(state,value){
40         state.sum += value
41     },
42     ADD_PERSON(state,value){
43         state.personList.unshift(value)
44     }
45 }
46 //准备state-用于存储数据
47 const state={
48     sum:0,//当前的和
49     school:'杰马课堂',
50     subject:'前端',
51     personList:[
52         {id:'001',name:'张三'}
53     ]
54 }
55 //准备getters-用于将state中的数据进行加工
56 const getters={
57     bigSum(state){
58         return state.sum*10
59 }
60     }
61 //创建并暴露store
62 export default new Vuex.Store({
63     actions,
64     mutations,
65     state,
66     getters
67 })

 

3.7 模块化编码+命名空间

 

  1.目的:让代码更好维护,让多种数据分类更加明确

 

  2.修改store.js

 1 const countAbout = {
 2   namespaced:true,//开启命名空间
 3   state:{x:1},
 4   mutations: { ... },
 5   actions: { ... },
 6   getters: {
 7     bigSum(state){
 8        return state.sum * 10
 9     }
10   }
11 }
12 
13 const personAbout = {
14   namespaced:true,//开启命名空间
15   state:{ ... },
16   mutations: { ... },
17   actions: { ... }
18 }
19 
20 const store = new Vuex.Store({
21   modules: {
22     countAbout,
23     personAbout
24   }
25 })

  3.开启命名空间后,组件中读取state数据:

//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取:
// 用 mapState 取 countAbout 中的state 必须加上 'countAbout'
...mapState('countAbout',['sum','school','subject']),

  4.开启命名空间后,组件中读取getters数据:

//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])

  5.开启命名空间后,组件中调用dispatch

//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})

  6.开启命名空间后,组件中调用commit

//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),

 

具体案例:

  ./store/index.js

 1 //该文件用于创建Vuex中最为核心的store
 2 
 3 //引入Vue
 4 import Vue from 'vue'
 5 //引入Vuex
 6 import Vuex from 'vuex'
 7 import countOptions from './count'
 8 import personOptions from './person'
 9 //使用Vuex
10 Vue.use(Vuex)
11 
12 //创建并暴露store
13 export default new Vuex.Store({
14     modules:{
15         countAbout:countOptions,
16         personAbout:personOptions
17     }
18 })

  ./store/count.js

 1 //求和相关的配置
 2 export default{
 3     namespaced:true,
 4     actions:{
 5         oddJia(context,value){
 6             if(context.state.sum % 2){
 7                 context.commit('ODDJIA',value)
 8             }
 9         },
10         waitJia(context,value){
11             setTimeout(() => {
12                 context.commit('WAITJIA',value)
13             }, 500);
14         }
15     },
16     mutations:{
17         JIA(state,value){
18             state.sum += value
19         },
20         JIAN(state,value){
21             state.sum -= value
22         },
23         ODDJIA(state,value){
24             state.sum += value
25         },
26         WAITJIA(state,value){
27             state.sum += value
28         },
29     },
30     state:{
31         sum:0,//当前的和
32         school:'杰马课堂',
33         subject:'前端',
34     },
35     getters:{
36         bigSum(state){
37             return state.sum*10
38         }
39     }
40 }

  ./store/person.js

 1 import axios from "axios"
 2 import { nanoid } from "nanoid"
 3 
 4 //人员管理相关的配置
 5 export default{
 6     namespaced:true,
 7     actions:{
 8         addPersonWang(context,value){
 9             if(value.name.indexOf('王')===0){
10                 context.commit('ADD_PERSON',value)
11             }else{
12                 alert('添加的人必须姓王')
13             }
14         },
15         //向服务器要一个人的名字
16         addPersonServe(context){
17             axios.get('http://api.uixsj.cn/hitokoto/get?type=social').then(
18                 response =>{
19                     context.commit('ADD_PERSON',{id:nanoid(),name:response.data})
20                 },
21                 error =>{
22                     alert(error.messgae)
23                 }
24             )
25         }
26     },
27 
28     mutations:{
29         ADD_PERSON(state,value){
30             state.personList.unshift(value)
31         }
32     },
33     state:{
34         personList:[
35             {id:'001',name:'张三'}
36         ]
37     },
38     getters:{
39         firstPersonName(state){
40             return state.personList[0].name
41         }
42     }
43 }

  Count.vue

 1 <template>
 2     <div>
 3         <h1>当前求和为:{{sum}}</h1>
 4         <h3>当前求和放大10倍为:{{bigSum}}</h3>
 5         <h3>我在{{school}},学习{{subject}}</h3>
 6         <h3 style="color: blue;">下方列表的总人数是:{{personList.length}}</h3>
 7         <select v-model="n">
 8             <option :value="1">1</option>
 9             <option :value="2">2</option>
10             <option :value="3">3</option>
11         </select>
12         <button @click="increment(n)">+</button>
13         <button @click="decrement(n)">-</button>
14         <button @click="incrementOdd(n)">当前求和为奇数再加</button>
15         <button @click="incrementWait(n)">等一等再加</button>
16     </div>
17 </template>
18 
19 <script>
20     import { mapState,mapGetters,mapMutations,mapActions } from 'vuex'
21     export default {
22         // eslint-disable-next-line vue/multi-word-component-names
23         name:'Count',
24         data(){
25             return{
26                 n:1,//用户选择的数字
27             }
28         },
29         computed:{
30             ...mapState('countAbout',['sum','school','subject']),
31             ...mapState('personAbout',['personList']),
32             ...mapGetters('countAbout',['bigSum'])
33         },
34         methods:{
35             ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
36             ...mapActions('countAbout',{incrementOdd:'oddJia',incrementWait:'waitJia'})
37         }
38     }
39 </script>
40 
41 <style>
42     button{
43         margin-left: 5px;
44     }
45 </style>

  Person.vue

 1 <template>
 2     <div>
 3         <h1>人员列表</h1>
 4         <h3 style="color:blue">上方组件的求和为:{{sum}}</h3>
 5         <h3>列表中第一个名字是:{{firstPersonName}}</h3>
 6         <input type="text" placeholder="请输入名字" v-model="name">
 7         <button @click="add">添加</button>
 8         <button @click="addWang">添加一个姓王的人</button>
 9         <button @click="addPersonServe">添加一个随机姓名</button>
10         <ul>
11             <li v-for="p in personList" :key="p.id">{{p.name}}</li>
12         </ul>
13     </div>
14 </template>
15 
16 <script>
17     import { nanoid } from 'nanoid'
18     import { mapGetters, mapState } from 'vuex'
19     export default {
20         // eslint-disable-next-line vue/multi-word-component-names
21         name:'Person',
22         data(){
23             return{
24                 name:''
25             }
26         },
27         computed:{
28             ...mapState('personAbout',['personList']),
29             ...mapState('countAbout',['sum']),
30             firstPersonName(){
31                 return this.$store.getters['personAbout/firstPersonName']
32             },
33             ...mapGetters('personAbout',['firstPersonName'])
34         },
35         methods:{
36             add(){
37                 const personObj={id:nanoid(),name:this.name}
38                 this.$store.commit('personAbout/ADD_PERSON',personObj)
39                 this.name=''
40             },
41             addWang(){
42                 const personObj={id:nanoid(),name:this.name}
43                 this.$store.dispatch('personAbout/addPersonWang',personObj)
44                 this.name=''
45             },
46             addPersonServe(){
47                 this.$store.dispatch('personAbout/addPersonServe')
48             }
49         }
50     }
51 </script>
52 
53 <style>
54 
55 </style>

  App.vue

 1 <template>
 2     <div>
 3         <Count></Count>
 4         <hr>
 5         <Person></Person>
 6     </div>
 7 </template>
 8 
 9 <script>
10     import Count from './components/Count'
11     import Person from './components/Person'
12     export default {
13         name:'App',
14         components:{Count,Person}
15     }
16 </script>
17 
18 <style>
19 
20 </style>

  main.js

 1 //引入Vue
 2 import Vue from 'vue'
 3 //引入App
 4 import App from './App.vue'
 5 //引入插件
 6 import vueResource from 'vue-resource'
 7 //引入store
 8 import store from './store/index'
 9 //关闭Vue的生产提示
10 Vue.config.productionTip = false
11 //使用插件
12 Vue.use(vueResource)
13 
14 //创建vm
15 new Vue({
16     el:'#root',
17     render: h => h(App),
18     store,
19     beforeCreate() {
20         Vue.prototype.$bus = this
21     },
22 })

 

4. 路由

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

4.1 基本使用

  1.安装vue-router,命令:npm i vue-router

  2.应用插件:Vue.use(VueRouter)

  3.编写router配置项:

 1 //引入VueRouter
 2 import VueRouter from 'vue-router'
 3 //引入Luyou 组件
 4 import About from '../components/About'
 5 import Home from '../components/Home'
 6 
 7 //创建router实例对象,去管理一组一组的路由规则
 8 const router = new VueRouter({
 9     routes:[
10         {
11             path:'/about',
12             component:About
13         },
14         {
15             path:'/home',
16             component:Home
17         }
18     ]
19 })
20 
21 //暴露router
22 export default router

  4.实现切换(active-class可配置高亮样式)

1 <router-link active-class="active" to="/about">About</router-link>

  5.指定展示位置 

1 <router-view></router-view> 

 

代码:

  /components/About.vue

 1 <template>
 2     
 3     <h2>我是About的内容</h2>
 4     
 5 </template>
 6 
 7 <script>
 8     export default {
 9         name:'About'
10     }
11 </script>
12 
13 <style>
14 
15 </style>

  /components/Home.vue

1 <template>
2     <h2>我是Home的内容</h2>
3 </template>
4 
5 <script>
6     export default {
7         name:'Home'
8     }
9 </script>

  /router/index.js

 1 // 该文件专门用于创建整个应用的路由器
 2 import VueRouter from 'vue-router'
 3 //引入组件
 4 import About from '../components/About'
 5 import Home from '../components/Home'
 6 
 7 //创建并暴露一个路由器
 8 export default new VueRouter({
 9     routes:[
10         {
11             path:'/about',
12             component:About
13         },
14         {
15             path:'/home',
16             component:Home
17         }
18     ]
19 })

  App.vue

 1 <template>
 2   <div>
 3     <div class="row">
 4       <div class="col-xs-offset-2 col-xs-8">
 5         <div class="page-header"><h2>Vue Router Demo</h2></div>
 6       </div>
 7     </div>
 8     <div class="row">
 9       <div class="col-xs-2 col-xs-offset-2">
10         <div class="list-group">
11                     <!-- 原始html中我们使用a标签实现页面的跳转 -->
12           <!-- <a class="list-group-item active" href="./about.html">About</a> -->
13           <!-- <a class="list-group-item" href="./home.html">Home</a> -->
14 
15                     <!-- Vue中借助router-link标签实现路由的切换 -->
16                     <router-link class="list-group-item" active-class="active" to="/about">About</router-link>
17           <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
18         </div>
19       </div>
20       <div class="col-xs-6">
21         <div class="panel">
22           <div class="panel-body">
23                         <!-- 指定组件的呈现位置 -->
24             <router-view></router-view>
25           </div>
26         </div>
27       </div>
28     </div>
29   </div>
30 </template>
31 
32 <script>
33     export default {
34         name:'App',
35     }
36 </script>

  main.js

 1 //引入Vue
 2 import Vue from 'vue'
 3 //引入App
 4 import App from './App.vue'
 5 //引入vue-router
 6 import VueRouter from 'vue-router'
 7 //引入路由器
 8 import router from './router/index'
 9 //关闭Vue的生产提示
10 Vue.config.productionTip = false
11 //应用插件
12 Vue.use(VueRouter)
13 
14 //创建vm
15 new Vue({
16     el:'#root',
17     render: h => h(App),
18     router:router
19 })

 

4.2 几个注意点

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

  2.通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
  3.每个组件都有自己的$route属性,里面存储着自己的路由信息。
  4.整个应用只有一个router,可以通过组件的$router属性获取到。

4.3 多级路由(嵌套路由)

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

 1 routes:[
 2     {
 3         path:'/about',
 4         component:About,
 5     },
 6     {
 7         path:'/home',
 8         component:Home,
 9         children:[ //通过children配置子级路由
10             {
11                 path:'news', //此处一定不要写:/news
12                 component:News
13             },
14             {
15                 path:'message',//此处一定不要写:/message
16                 component:Message
17             }
18         ]
19     }
20 ]

  2.跳转(要写完整路径):1 <router-link to="/home/news">News</router-link> 

  3.指定展示位置: 1 <router-view></router-view> 

代码:

  /components/Banner.vue

 1 <template>
 2     <div class="col-xs-offset-2 col-xs-8">
 3         <div class="page-header"><h2>Vue Router Demo</h2></div>
 4     </div>
 5 </template>
 6 
 7 <script>
 8     export default {
 9         name:'Banner'
10     }
11 </script>
12 
13 <style>
14 
15 </style>

  /pages/About.vue

 1 <template>
 2     
 3     <h2>我是About的内容</h2>
 4     
 5 </template>
 6 
 7 <script>
 8     export default {
 9         name:'About'
10     }
11 </script>
12 
13 <style>
14 
15 </style>

  /pages/Home.vue

 1 <template>
 2     <div>
 3         <h2>Home组件内容</h2>
 4         <div>
 5             <ul class="nav nav-tabs">
 6                 <li>
 7                     <router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
 8                 </li>
 9                 <li>
10                     <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
11                 </li>
12             </ul>
13             <ul>
14                 <router-view></router-view>
15             </ul>
16         </div>
17     </div>
18     
19 </template>
20 
21 <script>
22     export default {
23         name:'Home'
24     }   
25 </script>
26 
27 <style>
28 
29 </style>

  /pages/Message.vue

 1 <template>
 2     <div>
 3         <ul>
 4             <li>
 5                 <a href="/message1">message001</a>&nbsp;&nbsp;
 6             </li>
 7             <li>
 8                 <a href="/message2">message002</a>&nbsp;&nbsp;
 9             </li>
10             <li>
11                 <a href="/message/3">message003</a>&nbsp;&nbsp;
12             </li>
13         </ul>
14     </div>
15 </template>
16 
17 <script>
18     export default {
19         name:'Message'
20     }
21 </script>
22 
23 <style>
24 
25 </style>

  /pages/News.vue

 1 <template>
 2     <ul>
 3         <li>news001</li>
 4         <li>news002</li>
 5         <li>news003</li>
 6     </ul>
 7 </template>
 8 
 9 <script>
10     export default {
11         name:'News'
12     }
13 </script>
14 
15 <style>
16 
17 </style>

  /router/index.js

 1 //该文件用于创建整个应用的路由器
 2 import VueRouter from 'vue-router'
 3 //引入组件
 4 import About from '../pages/About'
 5 import Home from '../pages/Home'
 6 import News from '../pages/News'
 7 import Message from '../pages/Message'
 8 //创建并暴露一个路由器
 9 export default new VueRouter({
10     routes:[
11         {
12             path:'/about',
13             component:About
14         },
15         {
16             path:'/home',
17             component:Home,
18             children:[
19                 {
20                     path:'news',
21                     component:News
22                 },
23                 {
24                     path:'message',
25                     component:Message
26                 }
27             ]
28         }
29     ]
30 })

  App.vue

 1 <template>
 2     <div>
 3         <div class="row">
 4             <Banner></Banner>
 5         </div>
 6         <div class="row">
 7             <div class="col-xs-2 col-xs-offset-2">
 8                 <div class="list-group">
 9                     <!-- 原始html中我们使用a标签实现页面的跳转 -->
10                     <!-- <a class="list-group-item active" href="./about.html">About</a>
11                     <a class="list-group-item" href="./home.html">Home</a> -->
12 
13                     <!-- Vue中借助router-link标签实现路由的切换 -->
14                     <router-link class="list-group-item" active-class="active" to="/about">About</router-link>
15                     <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
16                 </div>
17             </div>
18             <div class="col-xs-6">
19                 <div class="panel">
20                     <div class="panel-body">
21                         <!-- 指定组件的呈现位置 -->
22                         <router-view></router-view>
23                     </div>
24                 </div>
25             </div>
26         </div>
27     </div>
28 </template>
29 
30 <script>
31     import Banner from './components/Banner'
32     export default {
33         name:'App',
34         components:{Banner}
35     }
36 </script>
37 
38 <style>
39 
40 </style>

  main.js

 1 //引入Vue
 2 import Vue from 'vue'
 3 //引入App
 4 import App from './App.vue'
 5 //引入vue-router
 6 import VueRouter from 'vue-router'
 7 //引入路由器
 8 import router from './router/index'
 9 //关闭Vue的生产提示
10 Vue.config.productionTip = false
11 //应用插件
12 Vue.use(VueRouter)
13 
14 //创建vm
15 new Vue({
16     el:'#root',
17     render: h => h(App),
18     router:router
19 })

4.4 路由的query参数

  1.传递参数

 1 <!-- 跳转并携带query参数,to的字符串写法 -->
 2 <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
 3                 
 4 <!-- 跳转并携带query参数,to的对象写法 -->
 5 <router-link 
 6     :to="{
 7         path:'/home/message/detail',
 8         query:{
 9            id:666,
10             title:'你好'
11         }
12     }"
13 >跳转</router-link>

  2.接收参数: 1 $route.query.id 2 $route.query.title 

 

案例:

  /components/Banner.vue

 1 <template>
 2     <div class="col-xs-offset-2 col-xs-8">
 3         <div class="page-header"><h2>Vue Router Demo</h2></div>
 4     </div>
 5 </template>
 6 
 7 <script>
 8     export default {
 9         name:'Banner'
10     }
11 </script>
12 
13 <style>
14 
15 </style>

  /pages/About.vue

 1 <template>
 2     
 3     <h2>我是About的内容</h2>
 4     
 5 </template>
 6 
 7 <script>
 8     export default {
 9         name:'About'
10     }
11 </script>
12 
13 <style>
14 
15 </style>

  /pages/Detail.vue

 1 <template>
 2     <ul>
 3         <li>消息编号:{{$route.query.id}}</li> 
 4         <li>消息标题:{{$route.query.title}}</li>
 5     </ul>
 6 </template>
 7 
 8 <script>
 9     export default {
10         name:'Detail',
11         mounted(){
12             console.log(this.$route)
13         }
14     }
15 </script>
16 
17 <style>
18 
19 </style>

  /pages/Home.vue

 1 <template>
 2     <div>
 3         <h2>Home组件内容</h2>
 4         <div>
 5             <ul class="nav nav-tabs">
 6                 <li>
 7                     <router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
 8                 </li>
 9                 <li>
10                     <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
11                 </li>
12             </ul>
13             <ul>
14                 <router-view></router-view>
15             </ul>
16         </div>
17     </div>
18     
19 </template>
20 
21 <script>
22     export default {
23         name:'Home'
24     }   
25 </script>
26 
27 <style>
28 
29 </style>

  /pages/Message.vue

 1 <template>
 2     <div>
 3         <ul>
 4             <li v-for="m in messageList" :key="m.id">
 5                 <!-- 跳转路由并携带query参数,to的字符串写法 -->
 6                 <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">
 7                     {{m.title}}
 8                 </router-link>&nbsp;&nbsp; -->
 9 
10                 <!-- 跳转路由并携带query参数,to的对象写法 -->
11                 <router-link :to="{
12                     path:'/home/message/detail',
13                     query:{
14                         id:m.id,
15                         title:m.title
16                     }
17                 }">
18                     {{m.title}}
19                 </router-link>&nbsp;&nbsp;
20             </li>
21         </ul>
22         <hr>
23         <router-view></router-view>
24     </div>
25 </template>
26 
27 <script>
28     export default {
29         name:'Message',
30         data(){
31             return{
32                 messageList:[
33                     {id:'001',title:'消息001'},
34                     {id:'002',title:'消息002'},
35                     {id:'003',title:'消息003'},
36                 ]
37             }
38         }
39     }
40 </script>
41 
42 <style>
43 
44 </style>

  /pages/News.vue

 1 <template>
 2     <ul>
 3         <li>news001</li>
 4         <li>news002</li>
 5         <li>news003</li>
 6     </ul>
 7 </template>
 8 
 9 <script>
10     export default {
11         name:'News'
12     }
13 </script>
14 
15 <style>
16 
17 </style>

  /router/index.js

 1 //该文件用于创建整个应用的路由器
 2 import VueRouter from 'vue-router'
 3 //引入组件
 4 import About from '../pages/About'
 5 import Home from '../pages/Home'
 6 import News from '../pages/News'
 7 import Message from '../pages/Message'
 8 import Detail from '../pages/Detail'
 9 //创建并暴露一个路由器
10 export default new VueRouter({
11     routes:[
12         {
13             path:'/about',
14             component:About
15         },
16         {
17             path:'/home',
18             component:Home,
19             children:[
20                 {
21                     path:'news',
22                     component:News
23                 },
24                 {
25                     path:'message',
26                     component:Message,
27                     children:[
28                         {
29                             path:'detail',
30                             component:Detail
31                         }
32                     ]
33                 }
34             ]
35         }
36     ]
37 })

  App.vue

 1 <template>
 2     <div>
 3         <div class="row">
 4             <Banner></Banner>
 5         </div>
 6         <div class="row">
 7             <div class="col-xs-2 col-xs-offset-2">
 8                 <div class="list-group">
 9                     <!-- 原始html中我们使用a标签实现页面的跳转 -->
10                     <!-- <a class="list-group-item active" href="./about.html">About</a>
11                     <a class="list-group-item" href="./home.html">Home</a> -->
12 
13                     <!-- Vue中借助router-link标签实现路由的切换 -->
14                     <router-link class="list-group-item" active-class="active" to="/about">About</router-link>
15                     <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
16                 </div>
17             </div>
18             <div class="col-xs-6">
19                 <div class="panel">
20                     <div class="panel-body">
21                         <!-- 指定组件的呈现位置 -->
22                         <router-view></router-view>
23                     </div>
24                 </div>
25             </div>
26         </div>
27     </div>
28 </template>
29 
30 <script>
31     import Banner from './components/Banner'
32     export default {
33         name:'App',
34         components:{Banner}
35     }
36 </script>
37 
38 <style>
39 
40 </style>

  main.js

 1 //引入Vue
 2 import Vue from 'vue'
 3 //引入App
 4 import App from './App.vue'
 5 //引入vue-router
 6 import VueRouter from 'vue-router'
 7 //引入路由器
 8 import router from './router/index'
 9 //关闭Vue的生产提示
10 Vue.config.productionTip = false
11 //应用插件
12 Vue.use(VueRouter)
13 
14 //创建vm
15 new Vue({
16     el:'#root',
17     render: h => h(App),
18     router:router
19 })

 

4.5 命名路由

  1. 作用:可以简化路由的跳转。

  2. 如何使用

    1. 给路由命名:
       1 {
       2     path:'/demo',
       3     component:Demo,
       4     children:[
       5         {
       6             path:'test',
       7             component:Test,
       8             children:[
       9                 {
      10                       name:'hello' //给路由命名
      11                     path:'welcome',
      12                     component:Hello,
      13                 }
      14             ]
      15         }
      16     ]
      17 }
    2. 简化跳转:
       1 <!--简化前,需要写完整的路径 -->
       2 <router-link to="/demo/test/welcome">跳转</router-link>
       3 
       4 <!--简化后,直接通过名字跳转 -->
       5 <router-link :to="{name:'hello'}">跳转</router-link>
       6 
       7 <!--简化写法配合传递参数 -->
       8 <router-link 
       9     :to="{
      10         name:'hello',
      11         query:{
      12            id:666,
      13             title:'你好'
      14         }
      15     }"
      16 >跳转</router-link>

       

4.6 路由的params参数

  1. 配置路由,声明接收params参数

     1 {
     2     path:'/home',
     3     component:Home,
     4     children:[
     5         {
     6             path:'news',
     7             component:News
     8         },
     9         {
    10             component:Message,
    11             children:[
    12                 {
    13                     name:'xiangqing',
    14                     path:'detail/:id/:title', //使用占位符声明接收params参数
    15                     component:Detail
    16                 }
    17             ]
    18         }
    19     ]
    20 }

     

  2. 传递参数

     1 <!-- 跳转路由并携带params参数,to的字符串写法 -->
     2                 <router-link :to="`/home/message/detail/${m.id}/${m.title}`">
     3                     {{m.title}}
     4                 </router-link>&nbsp;&nbsp;
     5 
     6 
     7 <!-- 跳转路由并携带params参数,to的对象写法 -->
     8                 <router-link :to="{
     9                     name:'xiangqing',
    10                     params:{
    11                         id:m.id,
    12                         title:m.title
    13                     }
    14                 }">
    15                     {{m.title}}
    16                 </router-link>&nbsp;&nbsp;

    特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

   3.接收参数

1 <template>
2     <ul>
3         <li>消息编号:{{$route.params.id}}</li> 
4         <li>消息标题:{{$route.params.title}}</li>
5     </ul>
6 </template>

4.7 路由的props配置

作用:让路由组件更方便的收到参数

 1  path:'message',
 2                     component:Message,
 3                     children:[
 4                         {   
 5                             name:'xiangqing',
 6                             path:'detail',
 7                             component:Detail,
 8 
 9                             //谁接收谁写props配置
10                             /* //props的第一种写法:值为对象,该对象中所有的key-value都会以props的形式传给Detail组件
11                             props:{a:1,b:'hello'} */
12 
13                             /* //props的第二种写法:值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件
14                             props:true */
15 
16                             //props的第三种写法,值为函数
17                             props($route){
18                                 return{id:$route.query.id,title:$route.query.title}
19                             }
20                         }
21                     ]

方便在要跳转去的组件里更简便的写法

 

跳转去组件的具体代码

 1 <template>
 2     <ul>
 3         <li>消息编号:{{id}}</li> 
 4         <li>消息标题:{{title}}</li>
 5     </ul>
 6 </template>
 7 
 8 <script>
 9     export default {
10         name:'Detail',
11         props:['id','title'],
12         mounted(){
13             console.log(this.$route)
14         }
15     }
16 </script>

4.8 <router-link>的replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
  3. 如何开启replace模式:<router-link replace .......>News</router-link>

4.9 编程式路由导航

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

  2. 具体编码:

     1 //$router的两个API
     2 this.$router.push({
     3     name:'xiangqing',
     4         params:{
     5             id:xxx,
     6             title:xxx
     7         }
     8 })
     9 
    10 this.$router.replace({
    11     name:'xiangqing',
    12         params:{
    13             id:xxx,
    14             title:xxx
    15         }
    16 })
    17 this.$router.forward() //前进
    18 this.$router.back() //后退
    19 this.$router.go() //可前进也可后退

     

案例代码:

  components\Banner.vue

 1 <template>
 2     <div class="col-xs-offset-2 col-xs-8">
 3         <div class="page-header">
 4             <h2>Vue Router Demo</h2>
 5             <button @click="back">前进</button>
 6             <button @click="forward">后退</button>
 7             <button @click="test">测试go</button>
 8         </div>
 9     </div>
10 </template>
11 
12 <script>
13     export default {
14         name:'Banner',
15         methods:{
16             back(){
17                 this.$router.back()
18             },
19             forward(){
20                 this.$router.forward()
21             },
22             test(){
23                 this.$router.go(3)//数字传几,就会前进或者后退几
24             }
25         }
26     }
27 </script>
28 
29 <style>
30 
31 </style>

  pages\About.vue

 1 <template>
 2     
 3     <h2>我是About的内容</h2>
 4     
 5 </template>
 6 
 7 <script>
 8     export default {
 9         name:'About'
10     }
11 </script>
12 
13 <style>
14 
15 </style>

  pages\Detail.vue

 1 <template>
 2     <ul>
 3         <li>消息编号:{{id}}</li> 
 4         <li>消息标题:{{title}}</li>
 5     </ul>
 6 </template>
 7 
 8 <script>
 9     export default {
10         name:'Detail',
11         props:['id','title'],
12         mounted(){
13             console.log(this.$route)
14         }
15     }
16 </script>
17 
18 <style>
19 
20 </style>

  pages\Home.vue

 1 <template>
 2     <div>
 3         <h2>Home组件内容</h2>
 4         <div>
 5             <ul class="nav nav-tabs">
 6                 <li>
 7                     <router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
 8                 </li>
 9                 <li>
10                     <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
11                 </li>
12             </ul>
13             <ul>
14                 <router-view></router-view>
15             </ul>
16         </div>
17     </div>
18     
19 </template>
20 
21 <script>
22     export default {
23         name:'Home'
24     }   
25 </script>
26 
27 <style>
28 
29 </style>

  pages\Message.vue

 1 <template>
 2     <div>
 3         <ul>
 4             <li v-for="m in messageList" :key="m.id">
 5                  <!-- 跳转路由并携带params参数,to的对象写法 -->
 6                 <router-link :to="{
 7                     name:'xiangqing',
 8                     params:{
 9                         id:m.id,
10                         title:m.title
11                     }
12                 }">
13                     {{m.title}}
14                 </router-link>&nbsp;&nbsp;
15                 <button @click="pushShow(m)">push查看</button>&nbsp;
16                 <button @click="replaceShow(m)">replace查看</button>
17             </li>
18         </ul>
19         <hr>
20         <router-view></router-view>
21     </div>
22 </template>
23 
24 <script>
25     export default {
26         name:'Message',
27         data(){
28             return{
29                 messageList:[
30                     {id:'001',title:'消息001'},
31                     {id:'002',title:'消息002'},
32                     {id:'003',title:'消息003'},
33                 ]
34             }
35         },
36         methods:{
37             pushShow(m){
38                 this.$router.push({
39                     name:'xiangqing',
40                     params:{
41                         id:m.id,
42                         title:m.title
43                     }
44                 })
45             },
46             replaceShow(m){
47                 this.$router.replace({
48                     name:'xiangqing',
49                     params:{
50                         id:m.id,
51                         title:m.title
52                     }
53                 })
54             }
55         }
56     }
57 </script>

  pages\News.vue

 1 <template>
 2     <ul>
 3         <li>news001</li>
 4         <li>news002</li>
 5         <li>news003</li>
 6     </ul>
 7 </template>
 8 
 9 <script>
10     export default {
11         name:'News'
12     }
13 </script>
14 
15 <style>
16 
17 </style>

  \router\index.js

 1 //该文件用于创建整个应用的路由器
 2 import VueRouter from 'vue-router'
 3 //引入组件
 4 import About from '../pages/About'
 5 import Home from '../pages/Home'
 6 import News from '../pages/News'
 7 import Message from '../pages/Message'
 8 import Detail from '../pages/Detail'
 9 //创建并暴露一个路由器
10 export default new VueRouter({
11     routes:[
12         {   
13             name:'guanyu',
14             path:'/about',
15             component:About
16         },
17         {
18             path:'/home',
19             component:Home,
20             children:[
21                 {
22                     path:'news',
23                     component:News
24                 },
25                 {
26                     path:'message',
27                     component:Message,
28                     children:[
29                         {   
30                             name:'xiangqing',
31                             path:'detail/:id/:title',
32                             component:Detail,
33 
34                             props($route){
35                                 return{id:$route.params.id,title:$route.params.title}
36                             }
37                         }
38                     ]
39                 }
40             ]
41         }
42     ]
43 })

  App.vue

<template>
    <div>
        <div class="row">
            <Banner></Banner>
        </div>
        <div class="row">
            <div class="col-xs-2 col-xs-offset-2">
                <div class="list-group">
                    <!-- 原始html中我们使用a标签实现页面的跳转 -->
                    <!-- <a class="list-group-item active" href="./about.html">About</a>
                    <a class="list-group-item" href="./home.html">Home</a> -->

                    <!-- Vue中借助router-link标签实现路由的切换 -->
                    <router-link class="list-group-item" active-class="active" :to="{name:'guanyu'}">About</router-link>
                    <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
                </div>
            </div>
            <div class="col-xs-6">
                <div class="panel">
                    <div class="panel-body">
                        <!-- 指定组件的呈现位置 -->
                        <router-view></router-view>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    import Banner from './components/Banner'
    export default {
        name:'App',
        components:{Banner}
    }
</script>

<style>

</style>

  main.js

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入vue-router
import VueRouter from 'vue-router'
//引入路由器
import router from './router/index'
//关闭Vue的生产提示
Vue.config.productionTip = false
//应用插件
Vue.use(VueRouter)

//创建vm
new Vue({
    el:'#root',
    render: h => h(App),
    router:router
})

4.10 缓存路由组件

  1. 作用:让不展示的路由组件保持挂载,不被销毁。

  2. 具体编码:

    要想清楚要缓存的路由组件在哪展示,在哪展示就加到哪

     这个 include 指的是组件名

1 <keep-alive include="News"> 
2     <router-view></router-view>
3 </keep-alive>

 

4.11 两个新的生命周期钩子

作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
具体名字:

    • activated路由组件被激活时触发。
    • deactivated路由组件失活时触发。

这两个生命周期钩子需要配合前面的缓存路由组件使用(没有缓存路由组件不起效果)

<template>
    <ul>
        <li :style="{opacity}">欢迎学习Vue</li>
        <li>news001 <input type="text"></li>
        <li>news002 <input type="text"></li>
        <li>news003 <input type="text"></li>
    </ul>
</template>

<script>
    export default {
        name:'News',
        data(){
            return{
                opacity:1
            }
        },
        /* beforeDestory(){
            clearInterval(this.timer)
        },
        mounted(){
            this.timer= setInterval(() => {
                this.opacity -=0.01
                if(this.opacity<=0) this.opacity=1
            }, 16);
        } */
        
        //激活
        activated(){
            this.timer= setInterval(() => {
                this.opacity -=0.01
                if(this.opacity<=0) this.opacity=1
            }, 16);
        },
        //失活
        deactivated(){
            clearInterval(this.timer)
        }
    }
</script>

<style>

</style>

4.12 路由守卫

  1. 作用:对路由进行权限控制

  2. 分类:全局守卫、独享守卫、组件内守卫

  3. 全局守卫:

    //全局前置守卫:初始化时执行、每次路由切换前执行
    router.beforeEach((to,from,next)=>{
        console.log('beforeEach',to,from)
        if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
            if(localStorage.getItem('school') === 'zhejiang'){ //权限控制的具体规则
                next() //放行
            }else{
                alert('暂无权限查看')
                // next({name:'guanyu'})
            }
        }else{
            next() //放行
        }
    })
    
    //全局后置守卫:初始化时执行、每次路由切换后执行
    router.afterEach((to,from)=>{
        console.log('afterEach',to,from)
        if(to.meta.title){ 
            document.title = to.meta.title //修改网页的title
        }else{
            document.title = 'vue_test'
        }
    })

    完整代码:

    //该文件用于创建整个应用的路由器
    import VueRouter from 'vue-router'
    //引入组件
    import About from '../pages/About'
    import Home from '../pages/Home'
    import News from '../pages/News'
    import Message from '../pages/Message'
    import Detail from '../pages/Detail'
    //创建并暴露一个路由器
    const router = new VueRouter({
        routes:[
            {   
                name:'guanyu',
                path:'/about',
                component:About,
                meta:{title:'关于'}
            },
            {
                name:'zhuye',
                path:'/home',
                component:Home,
                meta:{title:'主页'},
                children:[
                    {
                        name:'xinwen',
                        path:'news',
                        component:News,
                        meta:{isAuth:true,title:'新闻'}
                    },
                    {   
                        name:'xiaoxi',
                        path:'message',
                        component:Message,
                        meta:{isAuth:true,title:'消息'},
                        children:[
                            {   
                                name:'xiangqing',
                                path:'detail/:id/:title',
                                component:Detail,
                                meta:{title:'详情'},
                                props($route){
                                    return{id:$route.params.id,title:$route.params.title}
                                }
                            }
                        ]
                    }
                ]
            }
        ]
    })
    //全局前置路由守卫——初始化的时候被调用,每次路由切换之前被调用
    router.beforeEach((to,from,next)=>{
        console.log('前置路由守卫',to,from)
        if(to.meta.isAuth){
            if(localStorage.getItem('school') === 'atguigu'){
                next()
            }else{
                alert('学校名错误,无权限')
            }
        }else{
            next()
        }
    })
    
    //全局后置路由守卫——初始化的时候被调用,每次路由切换之后被调用
    router.afterEach((to,from)=>{
        console.log('后置路由守卫',to,from)
        document.title=to.meta.title || '杰马'
        
    })
    
    export default router
  4. 独享守卫:

    就是在 routes 子路由内写守卫

 {
            name:'zhuye',
            path:'/home',
            component:Home,
            meta:{title:'主页'},
            children:[
                {
                    name:'xinwen',
                    path:'news',
                    component:News,
                    meta:{isAuth:true,title:'新闻'},
                    /* //独享路由守卫
                    beforeEnter:(to,from,next)=>{
                        console.log('独享路由守卫',to,from)
                        if(to.meta.isAuth){
                            if(localStorage.getItem('school') === 'atguigu'){
                                next()
                            }else{
                                alert('学校名错误,无权限')
                            }
                        }else{
                            next()
                        }
                    } */
                },
  1. 组件内守卫:

    在具体组件内写守卫

//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}

具体代码:

<template>
    
    <h2>我是About的内容</h2>
    
</template>

<script>
    export default {
        name:'About',
        //通过路由规则,进入该组件时被调用
        beforeRouteEnter(to,from,next){
            console.log('About-beforeRouteEnter',to,from)
            if(to.meta.isAuth){
                if(localStorage.getItem('school') === 'atguigu'){
                next()
            }else{
                alert('学校名错误,无权限')
            }
        }else{
            next()
        }
    },
        //通过路由规则,离开该组件时被调用
        beforeRouteLeave(to,from,next){
            console.log('About-beforeRouteleave',to,from)
            next()
        }
    }
</script>

<style>

</style>

 

4.13 路由器的两种工作模式
对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。

hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。

hash模式:

地址中永远带着#号,不美观 。
若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
兼容性较好。
history模式:

地址干净,美观 。
兼容性和hash模式相比略差。
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。

const router = new VueRouter({
    mode:'history',//不写mode,默认就是hash
    routes:[
        {   
            name:'guanyu',
            path:'/about',
            component:About,
            meta:{isAuth:true,title:'关于'},
        },

 

 Vue3

1.创建Vue3.0工程

1.1使用 vue-cli 创建

官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve

1.2使用 vite 创建

官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite

vite官网:https://vitejs.cn

  • 什么是vite?—— 新一代前端构建工具。
  • 优势如下:
    • 开发环境中,无需打包操作,可快速的冷启动。
    • 轻量快速的热重载(HMR)。
    • 真正的按需编译,不再等待整个应用编译完成。
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev

src分析工程结构

//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'

//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)

//挂载
app.mount('#app')

 

2.常用 Composition API

2.1拉开序幕的setup

  1. 理解:Vue3.0中一个新的配置项,值为一个函数。
  2. setup是所有Composition API(组合API)“ 表演的舞台 ”
  3. 组件中所用到的:数据、方法等等,均要配置在setup中。
  4. setup函数的两种返回值:
    1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
    2. 若返回一个渲染函数:则可以自定义渲染内容。(了解)
  5. 注意点:
    1. 尽量不要与Vue2.x配置混用
      • Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。
      • 但在setup中不能访问到Vue2.x配置(data、methos、computed...)。
      • 如果有重名, setup优先。
    2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

App.vue

<template>
    <h1>个人信息</h1>
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <button @click="sayHello">自我介绍</button>
</template>

<script>
    
    export default {
        name: 'App',
        setup(){
            //数据
            let name='张三'
            let age=18

            //方法
            function sayHello() {
                alert(`我叫${name},我${age}岁了,你好啊`)
            }

            return{
                name,
                age,
                sayHello
            }
        }
    }
</script>

2.2ref函数

  • 作用: 定义一个响应式的数据
  • 语法: const xxx = ref(initValue)
    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
    • JS中操作数据: xxx.value
    • 模板中读取数据: 不需要.value,直接:<div></div>
  • 备注:
    • 接收的数据可以是:基本类型、也可以是对象类型。
    • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。
    • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。

App.vue

<template>
    <h1>个人信息</h1>
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <h3>工作种类:{{job.type}}</h3>
    <h3>工作薪水:{{job.salary}}</h3>
    <button @click="changeInfo">修改个人信息</button>
</template>

<script>
    import {ref} from 'vue'
    export default {
        name: 'App',
        setup(){
            //数据
            let name=ref('张三')
            let age=ref(18)
            let job=ref({
                type:'前端工程师',
                salary:'30K'
            })

            //方法
            function changeInfo(){
                name.value = '李四'
                age.value = 19.5
                job.value.type='UI设计师'
                job.value.salary='60K'
                console.log(name,age)
            }

            return{
                name,
                age,
                job,
                changeInfo
            }
        }
    }
</script>

 

2.3reactive函数

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
  • reactive定义的响应式数据是“深层次的”。
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

App.vue

<template>
    <h1>个人信息</h1>
    <h2>姓名:{{person.name}}</h2>
    <h2>年龄:{{person.age}}</h2>
    <h3>工作种类:{{person.job.type}}</h3>
    <h3>工作薪水:{{person.job.salary}}</h3>
    <h3>爱好:{{person.hobby}}</h3>
    <h4>测试数据c:{{person.job.a.b.c}}</h4>
    <button @click="changeInfo">修改个人信息</button>
</template>

<script>
    import {reactive} from 'vue'
    export default {
        name: 'App',
        setup(){
            //数据
            /* let name=ref('张三')
            let age=ref(18)
            let job=reactive({
                type:'前端工程师',
                salary:'30K',
                a:{
                    b:{
                        c:666
                    }
                }
            })
            let hobby=reactive(['抽烟','喝酒','烫头']) */

            let person=reactive({
                name:'张三',
                age:18,
                job:{
                    type:'前端工程师',
                    salary:'30K',
                    a:{
                        b:{
                            c:666
                        }
                    }
                },
                hobby:['抽烟','喝酒','烫头']
            })
            //方法
            function changeInfo(){
                person.name = '李四'
                person.age = 19.5
                person.job.type='UI设计师'
                person.job.salary='60K'
                person.job.a.b.c=999
                person.hobby[0]='学习'
                console.log(person.name,person.age)
            }

            return{
                person,
                changeInfo
            }
        }
    }
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

2.4Vue3.0中的响应式原理

vue2.x的响应式

  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

Object.defineProperty(data, 'count', {
    get () {}, 
    set () {}
})
  • 存在问题:

    • 新增属性、删除属性, 界面不会更新。
    • 直接通过下标修改数组, 界面不会自动更新。

Vue3.0的响应式

  • 实现原理:

    • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。

    • 通过Reflect(反射): 对源对象的属性进行操作。

    • MDN文档中描述的Proxy与Reflect:

      • Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

      • Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

new Proxy(data, {
    // 拦截读取属性值
    get (target, prop) {
        return Reflect.get(target, prop)
    },
    // 拦截设置属性值或添加新属性
    set (target, prop, value) {
        return Reflect.set(target, prop, value)
    },
    // 拦截删除属性
    deleteProperty (target, prop) {
        return Reflect.deleteProperty(target, prop)
    }
})

proxy.name = 'tom'   

2.5reactive对比ref

  • 从定义数据角度对比:
    • ref用来定义:基本类型数据。
    • reactive用来定义:对象(或数组)类型数据。
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象。
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value

2.6setup的两个注意点

  • setup执行的时机

    • 在beforeCreate之前执行一次,this是undefined。
  • setup的参数

    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
    • context:上下文对象
      • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
      • slots: 收到的插槽内容, 相当于 this.$slots
      • emit: 分发自定义事件的函数, 相当于 this.$emit

App.vue

<template>
    <Demo @hello="showHelloMsg" msg="你好啊" school="杰马课堂">
        <template v-slot:qwe>
            <span>尚硅谷</span>
        </template>
    </Demo>
</template>

<script>
    import Demo from './components/Demo'
    export default {
        name: 'App',
        components:{Demo},
        setup(){
            function showHelloMsg(value){
                alert(`你好啊,你触发了hello事件,我收到的参数是:${value}!`)
            }

            return{
            showHelloMsg
        }
        },

       
}
</script>

Demo.vue

<template>
    <h1>个人信息</h1>
    <h2>姓名:{{person.name}}</h2>
    <h2>年龄:{{person.age}}</h2>
    <button @click="test">测试触发一下Demo组件的hello事件</button>
</template>

<script>
    import {reactive} from 'vue'
    export default {
        name: 'Demo',
        props:['msg','school'],
        emits:['hello'],//告诉App我知道了你给我绑定了hello事件
        /* beforeCreate(){
            console.log('---beforeDestory---')
        }, */
        setup(props,context){
            //console.log('---setup---')
            //console.log('---setup---',props)
            //console.log('---context---',context)
            //console.log('---setup---',context.attrs)//详情见Vue2中的$attrs
            //console.log('---setup---',context.emit)//触发自定义事件的
            console.log('---setup---',context.slots)//插槽
            //数据
            let person=reactive({
                name:'张三',
                age:18,
            })
            //方法
            function test() {
                context.emit('hello',666)
            }
            

            return{
                person,
                test
         }
    }
}
</script>

2.7计算属性与监视

1.computed函数

  • 与Vue2.x中computed配置功能一致

App.vue

<template>
    <Demo></Demo>
</template>

<script>
    import Demo from './components/Demo'
    export default {
        name: 'App',
        components:{Demo},
}
</script>

Demo.vue

<template>
    <h1>个人信息</h1>
    姓:<input type="text" v-model="person.firstName"><br><br>
    名:<input type="text" v-model="person.LastName"><br><br>
    全名:<span>{{person.fullName}}</span><br>
    全名: <input type="text" v-model="person.fullName">
</template>

<script>
    import {reactive,computed} from 'vue'
    export default {
        name: 'Demo',
        /* computed:{
            fullName(){
                return this.person.firstName + this.person.LastName
            }
        }, */
        setup(){
            let person=reactive({
                firstName:'',
                LastName:''
            })
            //方法
           /*  //计算属性--简写(没有考虑计算属性被修改的情况)
            person.fullName=computed(()=>{
                return person.firstName+person.LastName
            }) */

            //计算属性--完整写法(考虑读和写)
            person.fullName=computed({
                get(){
                    return person.firstName+'-'+person.LastName
                },
                set(value){
                    const nameArr=value.split('-')
                    person.firstName=nameArr[0]
                    person.LastName=nameArr[1]
                }
            })
            
            return{
                person,
               
         }
    }
}
</script>

2.watch函数

  • 与Vue2.x中watch配置功能一致

  • 两个小“坑”:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
    • 监视reactive定义的响应式数据中某个属性时:deep配置有效。

监视ref定义的数据:

  App.vue:

<template>
    <Demo></Demo>
</template>

<script>
    import Demo from './components/Demo'
    export default {
        name: 'App',
        components:{Demo},
}
</script>

  Demo.vue

<template>
    <h2>当前求和为:{{sum}}</h2>
    <button @click="sum++">点击+1</button><hr>
    <h2>当前的信息为:{{msg}}</h2>
    <button @click="msg+='!'">修改信息</button>
</template>

<script>
    import {ref,watch} from 'vue'
    export default {
        name: 'Demo',
        //Vue2写法
        watch:{
            /* //简单写法
            sum(newValue,oldValue){
                console.log('sum的值发生变化了',newValue,oldValue)
            } */
            /* //完整写法
            sum:{
                Immediate:true,//立即监视
                deep:true,
                handler(newValue,oldValue){
                    console.log('sum的值发生变化了',newValue,oldValue)
                }
            } */
        },
        setup(){
            let sum=ref(0)
            let msg=ref('你好啊')
            //情况一:监视ref所定义的一个响应式数据
            watch(sum,(newValue,oldValue)=>{
                console.log('sum的值发生变化了',newValue,oldValue)
            },{immediate:true,deep:true})

            //情况二:监视ref所定义的多个响应式数据
            /* watch([sum,msg],(newValue,oldValue)=>{
                console.log('sum或msg的值发生变化了',newValue,oldValue)
            }) */
            return{
                sum,
                msg
         }
    }
}
</script>

监视reactive定义的数据

  Demo.vue

<template>
    <h2>姓名:{{person.name}}</h2>
    <h2>年龄:{{person.age}}</h2>
    <h2>薪资:{{person.job.j1.salary}}K</h2>
    <button @click="person.name+='~'">修改姓名</button>
    <button @click="person.age--">降低年龄</button>
    <button @click="person.job.j1.salary++">增长薪资</button>
</template>

<script>
    import {reactive,watch} from 'vue'
    export default {
        name: 'Demo',
        
        setup(){
            let person=reactive({
                name:'张三',
                age:88,
                job:{
                    j1:{
                        salary:20
                    }
                }
            })

            //监视reactive所定义的一个响应式数据的全部属性 注意:此处无法正确的获取oldValue
            /* watch(person,(newValue,oldValue)=>{
                console.log('person变化了',newValue,oldValue)
            },{deep:false})//此处的deep配置无效 */

            /* //监视reactive所定义的一个响应式数据中的某个属性
            watch(()=>person.age,(newValue,oldValue)=>{
                console.log('person的age变化了',newValue,oldValue)
            },{deep:false}) */

            //监视reactive所定义的一个响应式数据中的某些属性
            watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
                console.log('person的age变化了',newValue,oldValue)
            },{deep:false})

            //特殊情况
            watch(()=>person.job,(newValue,oldValue)=>{
                console.log('person的job变化了',newValue,oldValue)
            },{deep:true})
            return{
                person
         }
    }
}
</script>

3.watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
    const x1 = sum.value
    const x2 = person.age
    console.log('watchEffect配置的回调执行了')
})

完整代码:

Demo.vue

<template>
    <h2>当前求和为:{{sum}}</h2>
    <button @click="sum++">点击+1</button><hr>
    <h2>当前的信息为:{{msg}}</h2>
    <button @click="msg+='!'">修改信息</button>
    <h2>姓名:{{person.name}}</h2>
    <h2>年龄:{{person.age}}</h2>
    <h2>薪资:{{person.job.j1.salary}}K</h2>
    <button @click="person.name+='~'">修改姓名</button>
    <button @click="person.age--">降低年龄</button>
    <button @click="person.job.j1.salary++">增长薪资</button>
</template>

<script>
    // eslint-disable-next-line no-unused-vars
    import {reactive,ref,watch,watchEffect} from 'vue'
    export default {
        name: 'Demo',
        setup(){
            let sum=ref(0)
            let msg=ref('你好啊')
            let person=reactive({
                name:'张三',
                age:88,
                job:{
                    j1:{
                        salary:20
                    }
                }
            })
            /* watch(sum,(newValue,oldValue)=>{
                console.log('sum变化了',newValue,oldValue)
            },{}) */
            watchEffect(()=>{
                // eslint-disable-next-line no-unused-vars
                const x1=sum.value
                // eslint-disable-next-line no-unused-vars
                const x2=person.job.j1.salary
                console.log('watchEffect所指定的回调执行了')
            })
            return{
                sum,
                msg,
                person,
            }
    }
}
</script>

 

posted @ 2022-06-23 17:32  King_X  阅读(46)  评论(0)    收藏  举报