Vue2.0笔记

Vue2笔记

2023.11.27日撰

文章目录


注:

名词解释:

vm:Vue Model,代指Vue的实例对象

vc: VueComponent(非官方命名,尚硅谷讲师张天禹命名), 代指VueComponent构造函数创造的实例

一、Vue是什么?

1.官方解释

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

2.个人理解

Vue框架的核心理念是数据驱动视图,开发者不需要亲自操作DOM元素,一切操作DOM元素的行为都交给Vue进行,开发者只需要关注维护好数据,数据自然会致使视图发生变化。

二、初识Vue

  • 示例

    <!DOCTYPE html>
    <html lang="zh-CN">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>初识Vue</title>
      <script type="text/javascript" src="../js/vue.js"></script>
      <script type="text/javascript">
        Vue.config.productionTip = false // 阻止Vue启动时生产提示
      </script>
    </head>
        
    <body>
        <div id="root">
      		<h1>hello,{{message}}</h1>
      		<hr>
      		<a :href="url">点我去百度</a>
    	</div>
        <script>
        	new Vue({
          		el: `#root`,
         		data: {
            		message: 'world!',
            		url: `https://www.baidu.com`
          		}
        	})
      </script>
    </body>
    
    </html>
    

1.Vue对象

  • 在使用Vue时,第一步就需要为Vue准备一个容器,实际上这也是大多数框架使用前的第一步。这个容器可以是div,亦可以是span…只要该标签可以展示在页面上就可以行的通!在创建该标签时最好为该标签添加class属性或是id属性,无论是class属性还是id属性他们的值应当是唯一的!一个Vue实例只能掌控一个容器。

  • 如果你的容器准备好了,就可以进行第二步了!在该容器标签的底部创建script标签,在标签内书写创建Vue实例的Js代码,在new Vue()时一般不需要为实例vm进行接值,只需要在new时在Vue的构造函数内填入配置对象options即可!

2.options配置对象

​ Vue构造函数中的配置对象属性非常多,初识Vue不会全部进行使用!暂且介绍两个属性

  • el

    ​ el属性作用是为所在Vue实例指定需要接管哪个容器,该属性的value部分为字符串,字符串内容应当是css选择器且该选择器最好只能选中一个唯一的容器元素!

  • data

    ​ data属性为模板解析时提供数据,值得注意的是,data属性的value部分通常为对象,只要在data属性中配置了数据,Vue在模板解析时都可以通过插值语法、v-bind、v-model等等语法获取到并在模板中使用该属性的位置进行赋值替换!

  • el的另一种配置方法

    ​ 刚刚我们介绍了Vue中常规的el配置方式,el的作用是为vm指定需要接管的容器,el配置在option对象中则vm在被创建时就绑定了一个容器,该属性其实可以延迟绑定,但是有个小小的要求,就是在new Vue(options)时为vm进行接值,在合适的时机再为vm指定需要接管的容器,就像这样:

    const vm = new Vue({
      data:{
         value: `hello Vue!`
      }
    })
    vm.$mount('#root');
    
  • data的另一种配置方式

    ​ data属性的另一种配置方式为函数式,上一步的配置方式为对象式,两种配置方式在初学阶段效果是一样的,后续需要引入使用组件后该配置项必须为函数式,否则无法正常

    使用data内的数据。函数式配置方法如下:

    new Vue({
    	el:`#root`,
    	data(){
            return {
    			value : `hello Vue!`
           	}
    	}
    }
    

三、数据绑定

  • 示例

    <!DOCTYPE html>
    <html lang="zh-CN">
    
    <head>
      <meta charset="UTF-8">
      <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" :value="value">
        <!-- 双向绑定v-model只能应用在表单类元素上且具有value属性 -->
        双向数据绑定: <input type="text" v-model:value="value">
        插值语法: {{value}}
      </div>
    
      <script>
        new Vue({
          el: `#root`,
          data: {
            value: `hello Vue!`
          }
        })
    
      </script>
    
    </body>
    
    </html>
    

1.插值语法: {{ }}

  • 插值语法内可以放vm中data属性中已有的property,也可以存放js表达式进行运算,亦可以存放vm实例中的methods,值得注意的是,如果存放的是方法只需要在插值语法内填入方法名即可: ‘{{方法名}}’ ,Vue在模板解析时会自动完成对插值语法内值的替换。

  • 插值语法只能在标签体属性之外使用,不可在标签体属性之中使用

2.单向数据绑定:v-bind

​ v-bind作为通常在标签体内部进行使用,可以动态的获取vm中data属性,为标签体内部的属性进行赋值,v-bind通常简写为’:',例如 v-bind:href="url" 可以简写为 :href="url" ,他们的效果是一样的!

3.双向数据绑定:v-model

  • v-model只能应用于表单类元素的value属性之中,因为只有他们具有改变值的能力,v-model如果绑定在了表单类元素的value属性之中,v-model同样具有v-bind的作用,可以将vm中的data中的数据动态的解析到视图模板中,唯一不同的是,**v-model具有可以将表单内的value数据动态的修改到vm实例中data属性中对应的property属性,这样的影响是双向的!**免去了开发者频繁的操作DOM元素。

  • v-model:value="data" 可以简写为 v-model="data"

四、事件

1.事件的基本使用

  • 示例

    
      <div id="root">
         <!-- 不需要参数传递可以不写括号,此种写法默认会传递event对象 -->
        <button v-on:click="showInfo1">点我提示信息1</button>
         <!-- 此处如果回调函数内需要使用event对象并且需要写括号,则需要使用 '$event' 进行占位回调函数内
    		才可以收到event对象,否则无法接受,即使实参列表为空	
    	-->
        <button @click="showInfo2($event , 10086)">点我提示信息2</button>
      </div>
    
      <script>
        new Vue({
          el: `#root`,
          methods: {
            showInfo1(e) {
              console.log(e) // 此处如果形参部分没有括号也未进行传参会默认传递event对象
              alert('这是提示信息1')
            },
            showInfo2(e , number) {
              alert('这是提示信息2')
              console.log(e , number);
            },
          }
        })
      </script>
    
  • 用户与界面进行交互的动作统称为事件,根据不同的动作会产生各类不同的事件,例如点击事件、输入事件、鼠标进入…事件部分的基础知识属于JavaScript的范畴,在此就不再过多赘述,不明确者请移步JS基础。

  • Vue中想要使用事件需要借助v-on指令,例如点击事件的完整写法为v-on:click="方法名",值得注意的是,代码的书写位置应该在标签体的属性部分,当点击时会执行回调函数,回调函数的书写位置在Vue构造函数配置对象的methods属性中。当然,双引号内不只可以存放回调函数,也可以在引号内书写对data中已有数据的操作,如果你的逻辑代码不多的话,否则还是书写在回调函数内比较好!

  • Vue在执行回调函数时会进行传参,第一个参数默认为event对象,event对象中包含了本次事件的基本信息,关于event对象这里也不再展开赘述了,因为这是Js部分的知识。回调函数的调用方法不同决定着event对象是否作为实参进行传参,调用回调函数时函数名后不带括号Vue会自动将event作为实参传递,一旦加上括号,Vue便不再传递event对象,即使实参列表为空,此时想要Vue帮助传递需要使用’$event’进行占位。要注意占位符的书写位置!一般放在首位,将其他数据放置在后。

  • v-on:click="方法名"可以简写为@click="方法名",也就是v-on:可以被’@'代替,这适用于所有事件。

2.事件修饰符

  • 示例

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <script src="../js/vue.js"></script>
      <title>事件修饰符</title>
      <style>
        .outter {
          padding: 10px 10px;
          height: 100px;
          background-color: skyblue;
        }
        .inner {
          height: 50px;
          background-color: purple;
        }
        hr {
          margin: 20px 0;
          background-color: black;
        }
      </style>
    </head>
    <body>
      <!-- 
        vue中的事件修饰符
          1.prevent:阻止默认事件
          2.stop:阻止冒泡
          3.once:事件只触发一次
          4.capture: 使用事件捕获模式
          5.self:只有event.target是当前操作的元素时才触发事件
          6.passive: 事件处理函数在调用时不会阻止默认行为,立即调用
       -->
      <div id="root">
        <h1>阻止事件的默认行为</h1>
        <a href="https:www.baidu.com" @click.prevent="go">去百度</a>
        <hr>
        <h1>阻止冒泡</h1>
        <div class="outter" @click="show1">
          <div class="inner" @click.stop="show2"></div>
        </div>
        <hr>
        <h1>事件只触发一次</h1>
        <button @click.once="tips">点我提示</button>
        <hr>
        <h1>使用事件捕获模式</h1>
        <div class="outter" @click.capture="show1">
          <div class="inner" @click="show2"></div>
        </div>
      </div>
      <script>
        new Vue({
          el: `#root`,
          data: {
    
          },
          methods: {
            go() {
              console.log(`去百度`);
            },
            show1() {
              console.log(1);
            },
            show2() {
              console.log(2);
            },
            tips() {
              alert("hello");
            },
          }
        })
      </script>
    </body>
    
    </html>
    
  • 在原生的js中,关于事件有许多配置项,例如阻止元素的默认行为、阻止冒泡、使用事件捕获模式等等,想要在Vue项目中同样配置这些,不必获取event对象进行亲自操作,因为Vue框架就是让开发者不必关注操作DOM。在Vue中想要解决这些配置,需要在事件名后书写修饰符解决这些配置。

  • Vue的事件修饰符书写方式为:@click.prevent="方法",修饰符可以加装多个!例如表单中的按钮只能点击一次并且阻止默认行为:@click.prevent.once="方法"

  • 常用的修饰符

    • prevent:取消元素的默认行为
    • once:事件只触发一次
    • stop:阻止事件冒泡
    • capture:使用事件捕获模式
    • self:只有event.target是当前操作时才触发事件
    • passive:事件处理函数在调用时不会阻止默认行为,立即执行默认行为再进行函数方法的调用

3.按键修饰符

  • 实例

    <!DOCTYPE html>
    <html lang="zh-CN">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <script src="../js/vue.js"></script>
      <title>键盘事件</title>
    </head>
    <body>
        <div id="root">
        <input type="text" name="name" placeholder="按下回车打印" @keyup.enter="print">
        <input type="text" name="name" placeholder="按下回车打印" @keyup.13="print">
      </div>
      <script>
        new Vue({
          el: `#root`,
          data: {
          },
          methods: {
            print(e) {
              console.log(e.target.value);
            }
          }
        })
      </script>
    </body>
    </html>
    
  • 键盘相关的事件处理Vue提供了类似于事件修饰符的操作,在上述案例中如果只需要回车按键才触发keyup事件,只需要在事件后添加按键名或keyCode(按键码),例如@keyup.enter="方法"@keyup.13=“方法”直接跟keyCode的形式已经废弃,官方推荐跟按键名的形式进行使用,但是Vue对按键名的封装是有限的,仅有以下几种可以直接进行使用:

    • enter
    • rab
    • delete
    • esc
    • space
    • up
    • down
    • left
    • right

五、Vue计算属性

  • 示例

    <body>
      <div id="root">
        <input type="text" name="" id="" v-model:value="firstName"><br>
        <input type="text" name="" id="" v-model:value="lastName"><br>
        <div>{{fullName}}</div>
      </div>
    
      <script>
        const vm = new Vue({
          el: `#root`,
          data: {
            firstName: ``,
            lastName: ``,
          },
          computed: {
            fullName: {
              get() {
                return this.firstName.substring(0, 3) + `-` + this.lastName
              },
              set(value) {
                const names = value.split('-')
                this.firstName = names[0]
                this.lastName = names[1]
              }
            }
          }
    
        })
      </script>
    </body>
    

1.何为计算属性

​ 在实际的开发中,元数据中通常不会包含全部的所需数据,某些数据是需要通过对元数据进行运算或拼接才能获得,例如购物车中小计,需要对每项的总价进行运算。当然这些需要被计算的属性可以通过在插值语法直接计算得来,但是这些过于繁琐,对此,Vue提供了一个options配置属性:computed,在computed中定义的属性可以像data配置属性中配置的数据一样进行使用,可以直接应用在插值语法、v-bind等处,Vue解析模板时将计算完成的数据填入模板。

2.computed的使用

  • computed配置属性中的property通常有两种配置方式,对象式和函数式,两种配置方式略有差异,可根据不同的使用场景进行选择。计算属性值的获取只能通过get方法,设置值自能通过set方法(有点像JavaBean),如果没有set方法则无法为此赋值。

    • 对象式

      • 对象式的配置方法需要在定义计算属性时提供get()和set()方法,set方法可以不做配置,这通常在计算属性只读的场景下配置,但采用对象式配置get方法不可不做配置,否则Vue将无法读取到该计算属性,配置该计算属性也就没有了意义,不是吗?如果单单配置get方法,采用对象式的配置方法略微麻烦,通常采用函数式的配置方法。注意get函数的返回值作为计算属性的值对象式的配方法如下:

        const vm = new Vue({
              el: `#root`,
              data: {
                firstName: ``,
                lastName: ``,
              },
              computed: {
                fullName: {
                  get() {
                    return this.firstName.substring(0, 3) + `-` + this.lastName
                  },
                  set(value) {
                    const names = value.split('-')
                    this.firstName = names[0]
                    this.lastName = names[1]
                  }
                }
              }
        })
        
    • 函数式

      • 函数式的配置方法较为轻松,属于简写方式。仅需在computed配置属性中提供与计算属性同名的方法,要注意为此方法提供return返回值,此返回值作为计算属性的值。此种写法适用于只读的计算属性,因为此种方法无法为计算属性提供set方法。函数式计算属性的配置如下:

        <body>
          <div id="root">
            <input type="text" name="" id="" v-model:value="firstName"><br>
            <input type="text" name="" id="" v-model:value="lastName"><br>
            <div>{{fullName}}</div>
          </div>
          <script>
            const vm = new Vue({
              el: `#root`,
              data: {
                firstName: ``,
                lastName: ``,
              },
              computed: {
                // 注意,此种方式前置条件是不需要set计算属性
                fullName() {
                  return this.firstName.substring(0, 3) + `-` + this.lastName
                }
              }
            })
          </script>
        </body>
        

六、监视属性

  • 示例

    <body>
      <div id="root">
        <h1>今天天气很{{info}}</h1>
        <button @click="changeWeather">点击切换天气</button>
      </div>
    
      <script>
        const vm = new Vue({
          el: `#root`,
          data: {
            isflag: true
          },
          computed: {
            info() {
              return this.isflag ? '炎热' : '凉爽'
            }
          },
          methods: {
            changeWeather() {
              this.isflag = !this.isflag
            }
          },
          watch: {
            // 第一种配置方式
            // info: {
            //   immediate: true,//immediate属性为true则初始化时就调用一次
           	//   deep:true, //开启深度监视
            //   handler(newValue, oldValue) {
            //     console.log('info值发生了变化,从' + oldValue + '变为了' + newValue)
            //   }
            // }
              
            // 第二种配置方式
            // 注意,此处简写同样要求只有检测函数存在,无法配置监视器 的属性
            //info(newValue, oldValue) {
            //		console.log('info值发生了变化,从' + oldValue + '变为了' + newValue)
            //}
          }
        })
        // 此种方式同样可配置
        vm.$watch('info', {
          immediate: true, 
          deep:true,
          handler(newValue, oldValue) {
            console.log('info值发生了变化,从' + oldValue + '变为了' + newValue)
          }
        })
      </script>
    </body>
    

1.何为监视属性?

​ 开发中某些数据的值的变化可能与某些事件或行为发生绑定,开发者往往要针对这些变化撰写一些逻辑代码,那么此时监视属性watch就派上用场了,"监视属性"这四个字要用动词的视角来理解,监视属性并不为Vue提供数据,而是对已有数据进行监视,无论该数据是data中配置的数据还是我们刚刚学习的计算属性,都可以进行监视。只要被监视的数据发生变化,就会触发配制好的函数调用。

2.watch的使用

  • 想监测某个数据则需要在配置属性watch中进行配置,同计算属性的配置方法类似,唯一不同的是此处的key必须是data或computed中已有的数据。value部分同样有常规和简写两种方法。

    • 常规配置

      const vm = new Vue({
            el: `#root`,
            data: {
              isflag: true
            },
            computed: {
              info() {
                return this.isflag ? '炎热' : '凉爽'
              }
            },
            methods: {
              changeWeather() {
                this.isflag = !this.isflag
              }
            },
            watch: {
              // 第一种配置方式
               info: {
                 immediate: true,//immediate属性为true则初始化时就调用一次
             	   deep:true, //开启深度监视
                 handler(newValue, oldValue) {
                   console.log('info值发生了变化,从' + oldValue + '变为了' + newValue)
                 }
               }
            }
          })
      
      • 只要被监测的数据发生变化Vue就会调用名为handler的回调函数,并为此函数传递两个参数,分别为改变后的新值和旧值,可以根据需要进行选择接值。
    • 简写

      
          const vm = new Vue({
            el: `#root`,
            data: {
              isflag: true
            },
            computed: {
              info() {
                return this.isflag ? '炎热' : '凉爽'
              }
            },
            methods: {
              changeWeather() {
                this.isflag = !this.isflag
              }
            },
            watch: {    
              // 第二种配置方式
              // 注意,此处简写同样要求只有检测函数存在,无法配置监视器 的属性
              info(newValue, oldValue) {
              	console.log('info值发生了变化,从' + oldValue + '变为了' + newValue)
             	}
            }
          })
      
      • 简写的配置方式虽然方便,但是丧失了对其他属性的配置,只能配置回调函数
  • 要注意如果被监测的对象是**引用数据类型,**则Vue默认只会关注对象的地址是否发生了变化,而导致引用数据类型内部的属性发生变化监测无效,如果需要监测内部属性则需要开启深度监视!deep:true

七、Vue中的条件渲染

1.v-if

  • 示例

    <body>
        <div id="root">
    		<button @click="isFlag = !isFlag">点我显示input</button>
    		<input v-if="isFlag" type="text"/>  
        </div>
    	<Script>
        	new Vue({
                el:"root",
                data:{
                    isFlag:false
                }
            })
        
        </Script>
    </body>
    
  • v-if会判断双引号内数据的布尔值决定该元素是否渲染至模板中,如果布尔值为假则该元素不会出现在html页面中。双引号中也可以放简单的js表达式和函数。

2.v-show

  • 示例

    <body>
        <div id="root">
    		<button @click="isFlag = !isFlag">点我显示input</button>
    		<input v-show="isFlag" type="text"/>  
        </div>
    	<Script>
        	new Vue({
                el:"root",
                data:{
                    isFlag:false
                }
            })
        
        </Script>
    </body>
    
  • v-show的用法与v-if一致,唯一不同的是,v-show中的布尔值如果为假该元素依然会渲染至页面中,但display属性为none,故依然不会在页面上显示,如果某一元素需要频繁的显示和隐藏,显然使用v-show比较合适,因为这样效率会高一些。

八、列表渲染 v-for

  • 示例

    <body>
      <div id="root">
        <ul>
          <li v-for="(p,index) in persons" :key="p.id">{{p.name}}-{{p.age}}</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 },
              { id: '004', name: '赵六', age: 21 },
            ]
          }
        })
      </script>
    </body>
    
  • v-for

    • v-for指令会基于已有数据进行循环遍历,该数据可以是对象、数组、字符串,通常会使用数组进行遍历,值得注意的是要给遍历项添加一个唯一的key值,该值最好是唯一的,否则会因为Vue的复用机制导致元素渲染错乱,如果遍历项不会再进行增删操作则可以使用index作为key值,但我们通常不会那么做。

九、数组更新监测

  • Vue中如果对数组直接通过索引访问元素并重新赋值的方式并不会触发视图更新!因为js的限制,Vue不能直接对引用数据类型的变化。Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

    • push()

    • pop()

    • unshift()

    • shift()

    • splice()

    • sort()

    • reverse()

      • 这些方法都有一个共同的特点,就是他们的调用都会导致数组自身发生变化!

      • 如果需要调用不会导致数组自身发生变化的方法,例如filte,仅需在方法的返回值处使用原数组进行接值,Vue对此进行了一些优化、这样做效率依然非常高。例如:

        example1.items = example1.items.filter(function (item) {
          return item.message.match(/Foo/)
        })
        

十、浅谈Vue中的数据监测

  • 简单模拟数据监测

     <script>
        let data = {
          name: '尚硅谷',
          address: '宏福科技园'
        }
    
        const obs = new Observer(data)
    
        let vm = {}
        vm = data = obs
    
    
        function Observer(obj) {
          const keys = Object.keys(obj)
          keys.forEach((item) => {
            Object.defineProperty(this, item, {
              get() {
                return obj[item]
              },
              set(val) {
                console.log(`${item}被改变了,更新dom...`);
                obj[item] = val
              }
            })
          })
        }
      </script>
    
  • Vue中通过改变数据就可以驱动视图更新,实现原理依赖了Object中的一个方法:defineProperty(),该方法可以对已有对象进行追加属性,进过该方法追加后的属性与初始化时定义的属性有所差别,追加的属性具有封装性,类似于JavaBean,不通过get/set方法无法对该属性进行访问、修改,Vue利用了这一特点往ObServe对象中追加属性,追加完成后再给vm赋值,使vm具有对数据检测的能力,一旦有数据改动,必然会经过set方法,在set方法中调用对应的模板解析方法即可实现由数据驱动的模板重解析。

1.Vue.set()

  • 通过刚刚的数据监测原理,你应该理解直接通过给vm身上绑定属性的方法不会有响应式的数据,因为这种方式不是通过defineProperty追加的属性,也就是说Vue不能监测到该属性的变化,更不会根据数据变化来响应页面,为此,Vue提供了set()方法用来追加data数据。

  • Vue.set()不能给Vue的实例对象及根属性添加新属性!

  • Vue.set()的使用如下:

      <script>
        const vm = new Vue({
          el: `#root`,
          data: {
            student: {
              name: 'tom',
              age: 21
            }
          },
          methods: {
            addSex() {
              Vue.set(this.student, 'sex', '男')
            }
          },
        })
        // 此种方法亦可以追加属性
        vm.$set(vm.student , 'address' , '北京')
        
      </script>
    

十一、自定义指令

1.什么是自定义指令

​ 时以至此,我们使用过许多Vue给出的指令,例如v-if,v-bind等等,Vue的宗旨就是希望我们通过这些指令免去繁琐的DOM操作,但在有些及特殊场景下,这些指令无法满足我们的需求!避免不了亲自对DOM的操作,此时即可根据我们的需求来自定义指令并应用在元素上来满足我们的需求。

2.directives

  • 示例:自定义扩大数据10倍指令

    <body>
      <div id="root">
        <h1>n当前值是 <span v-text="n"></span></h1>
        <h1>n扩大10倍后是 <span v-big="n"></span></h1>
        <button @click="n++">n增加1</button>
      </div>
    
      <script>
        new Vue({
          el: `#root`,
          data: {
            n: 1
          },
          directives: {
            big(element, binding) {
              element.innerText = binding.value * 10
            }
          }
        })
    
      </script>
    </body>
    
    • Vue在解析模板时遇到我们自定义的指令会执行我们所定义的回调函数并固定传递两个参数,指令使用时所在元素的element对象及双引号内的值,通过这两个参数我们可以对DOM进行操作!
  • Vue中想要自定义指令,需要借助配置属性directives来实现,在该配置属性中定义的property可以在元素中使用并完成我们自己定义的效果,在刚刚的示例中,我们使用了函数式自定义指令的方式,在Vue中使用函数式通常意味着该形式为简写形式,是的,我们的实例中实则是种简写形式,也就意味着不能配置更多信息。

3.对象式自定义指令

  • 示例:屏幕刷新与数据变化input框自动获取焦点

    <body>
      <!-- 
          需求:自定义一个v-fbind指令,添加到表单上时表单会自动获取焦点
          3个过程:
          bind:指令与元素成功绑定时
          inserted:指定所在元素被成功插入时
          update:指令所在的模板被重新解析时
       -->
      <div id="root">
        <h1>n现在的值是: <span v-text="n"></span></h1>
        <button @click="n++">点我n+1</button>
        <input type="text" v-fbind="n">
      </div>
    
      <script>
        new Vue({
          el: `#root`,
          data: {
            n: 1
          },
          directives: {
            fbind: {
              // 指令与元素成功绑定时
              bind(element, binding) {
                element.value = binding.value
              },
              // 指定所在元素被成功插入时
              inserted(element, binding) {
                element.focus()
              },
              // 指令所在的模板被重新解析时
              update(element, binding) {
                element.value = binding.value
                element.focus()
              }
            }
          }
        })
      </script>
    
    • 对象式配置方式为完整的指令配置方式,他们与函数式自定义指令不同的是多了对时间点的选择,当指令第一次与元素进行绑定时bind函数会被调用,同时传递两个参数:element bingding,这与函数式是一样的!后面两个函数也是如此。
    • 您可以能会有些许疑惑,为什么会用此案例来讲解自定义指令呢?感觉普通的函数式也能办到!但实际并非如此,普通函数式自定义指令默认配置的函数为bind函数,也就是指令与函数绑定时就会被调用一次!我们的需求是屏幕刷新后input获得焦点,但此时是函数式配置,指令与函数绑定时该元素还未被模板解析器放置在页面上!自然也不会获得焦点!但是我此时的配置方式为对象式,可以选择当指令所在元素被模板解析器插入在页面时再执行获取焦点的方法!此时便可以成功获取焦点~
  • 对象式自定义指令似乎与函数式截然不同,三个directives的配置属性都是函数,这些函数会在程序运行周期内的不同时间点被调用,就像到时间了函数被勾了一下,这样的函数也称为钩子函数(个人理解),在我们后续学习生命周期还会有许多的钩子函数等着我们学习~

4.全局自定义指令

  • 上两个案例中采用的自定义指令的方式是局部的,也就是定义在哪个vm实例中那个对应vm接管的容器内才可以使用自定义指令!想要所有vm实例都有共同的自定义指令,可以采取往Vue构造函数中添加自定义指令!这样我们的所有vm都具有自定义指令,全局配置方式如下:

     //定义全局指令
        Vue.directive('fbind2', {
          // 指令与元素成功绑定时
          bind(element, binding) {
            element.value = binding.value
          },
          // 指定所在元素被成功插入时
          inserted(element, binding) {
            element.focus()
          },
          // 指令所在的模板被重新解析时
          update(element, binding) {
            element.value = binding.value
          }
        })
    

十二、MVVM模型

对MVVM模型的理解

​ 经过长时间的学习,想信你对Vue的工作原理略微了解了,Vue的工作无非是接管数据,然后对数据进行处理、加工然后挂载到页面中。MVVM模型并非Vue独创,而是参考了MVVM模型进行开发!M V VM这是三个词汇的简写,核心理念是元数据Model经过ViewModel加工再挂载到页面中,而Vue实例就是负责加工的工厂,这也是Vue为什么称Vue实例为vm的原因!

十三、生命周期

1.什么是生命周期?

  • 生命周期的概念相信你不是第一次接触(毕竟学习完js再来学习Vue),Vue实例在创建过程中会经历许多阶段,例如在页面挂载前,页面挂载后,vm销毁前等等,Vue提供了8个时间节点供开发者使用,开发者可以自行决定在不同的时间点让程序做什么事情,这也给了开发者深度DIY的可能。

2.八个生命周期钩子函数

  • 数据代理、数据监测发生前后:beforeCreate created
  • DOM挂载前后:beforeMount mounted
    • 在页面完成后通常可以开始执行各类异步任务,开启定时器等
  • 模板更新前后:beforeUpdate updated
  • vm实例销毁前后:beforeDestroy destroyed
    • vm实例销毁前应做好各类收尾阶段,例如清除计时器

十四、组件

历经千山万险终于来到了组件的学习,组件是Vue中非常重要的篇章,也是构建大型项目必须学习的内容

1.什么是组件?

  • 组件的定义----实现应用中局部功能代码和资源的集合
  • 这个集合包括该局部功能完整的样式(CSS)、结构(HTML)、动作(Js)
  • 组件由vm进行同一管理和配置

2.Vue中怎么使用组件?

  • 示例-非单文件组件

    <body>
      <div id="root">
         <!-- 组件注册时的名字! --> 
        <school></school>
        <hr>
        <student></student>
      </div>
      <!-- 
          注意:
          1.在组件中定义data属性,必须使用函数的形式、使用函数的形式可以有效避免多个组件指向同一个对象
          2.组件定义可以简写、直接将配置项定义成一个对象、注册时Vue会自动帮我们进行定义
          
       -->
      <script>
    	//定义school组件
        const school = Vue.extend({
          template: `
          <div>
            <h1>学校名称: <span v-text="name"></span></h1>
            <h1>学校地址: <span v-text="address"></span></h1>
            </div>
            `,
          data() {
            return {
              name: '尚硅谷',
              address: '宏福科技园'
            }
          }
        })
        //定义student组件
        const student = Vue.extend({
          template: `
            <div>
              <h1>学生姓名: <span v-text="name"></span></h1>
              <h1>学生性别: <span v-text="sex"></span></h1>
              </div>
              `,
          data() {
            return {
              name: 'Bob',
              sex: '男'
            }
          }
        })
        //全局注册
        Vue.component('student', student)
        new Vue({
          el: `#root`,
          // 局部注册
          components: {
            school
          }
        })
      </script>
    </body>
    
  • 组件的使用大致分为三步,定义组件、注册组件、应用组件,这三步应按顺序书写,否则可能会致使发生不必要的Bug

  • 定义组件

     const student = Vue.extend({
          template: `
            <div>
              <h1>学生姓名: <span v-text="name"></span></h1>
              <h1>学生性别: <span v-text="sex"></span></h1>
              </div>
              `,
          data() {
            return {
              name: 'Bob',
              sex: '男'
            }
          }
        })
    
    • 组件的options配置类与Vue的options配置类基本一致,唯一不同的是,组件的配置类中不能书写el配置项,Vue这样的设计是合理的,组件的核心是能够对某一局部功能进行复用,一旦为组件绑定el,组件只能为对应容器进行服务,那也没必要使用组件了,不是吗?
    • template属性的value为字符串,作为该组件的结构填入DOM
    • **Vue.extend()函数返回的并非是组件实例,而是VueComponent构造函数,此后我们简称为vc。每次返回的都是全新的vc!**这也意味着每个组件的构造函数都是唯一的,并非像Vue构造函数一样是单例的。
  • 注册组件

    //全局注册
    Vue.component('student', student)
    new Vue({
          el: `#root`,
          // 局部注册
          components: {
            school //此处简写,原写法为 school:school,意味着school组件注册为scool标签使用 js允许这种形式的属性写成1个
          }
    })
    
    • 注册组件其实很好理解,通过配置vm中的components使得vm握有组件的构造函数,使得vm能有创建vc的能力,全局注册也是如此,只不过手握组件的对象换到了Vue的原型之中,这样所有的vm都有创建全局注册组件的能力
    • 注意组件配置时,key作为为组件的名字,key写成什么形式,DOM中就应该书写成什么形式
  • 应用组件

      <div id="root">
         <!-- 组件注册时的名字! --> 
        <school></school>
        <hr>
        <student></student>
      </div>
    
    • 在使用组件时只需要以组件注册时的名字作为标签使用即可!**值得注意的是,在非单文件组件的页面中使用标签时要采用双标签的结构,使用单标签会有Bug。**在脚手架环境下单标签可正常使用!(非单文件组件指组件不作为单独的文件定义,而是多个组件定义在一个文件中)
    • 模板解析器在解析组件标签时才能真正new出vc的实例!

3.单文件组件

  • 示例:单文件组件School.vue

    <template>
      <div class="school">
        <h1>学校名称: <span v-text="name"></span></h1>
        <h1>学校地址: <span v-text="address"></span></h1>
      </div>
    </template>
    
    <script>
    export default {
      name:'School',
      data() {
        return {
          name:'尚硅谷',
          address:'宏福科技园'
        }
      },
    }
    </script>
    
    <style scpoed>
      .school{
        background-color: rgba(211, 234, 12, 0.7);
      }
    </style>
    
  • **Vue的单文件组件是Vue中非常强大的组件化开发方式!**Vue创造了以vue为结尾的文件格式,该文件中仅书写一个组件,且定义的方式略有不同。

  • 每个组件文件中需要配置如下三个标签

    • template
      • 书写该组件的HTML结构
      • 注意该标签的子元素只能有一个!
    • script
      • 书写该组件的Js代码,此处使用了ES6模块化的默认暴露方式,将组件的定义对象暴露出去供其父组件引用。配置内容与非单文件组件一致,注意template没必要书写,vue文件可以直接在template标签内书写HTML结构
    • style
      • 通常只书写该组件中使用到的结构的CSS代码
  • 父组件的引用使用方法:

    <template>
      <div>
        <School></School>
      </div>
    </template>
    
    <script>
      import School from './School.vue' 
    export default {
      name:'App',
      components:{
        School
      }
    }
    </script>
    
    • 同非单文件组件注册组件的方法一致,唯一的区别是需要使用ES6模块化导入该组件。

4.Vue cil脚手架

学习本章需要具有nodejs、npm基础

关于cil的介绍建议查阅Vue官方文档:安装 — Vue.js (vuejs.org)

  • Vue单文件组件固然是强大的组件化开发方式!但浏览器并不能直接识别vue文件,而是需要搭建工作流来将vue文件转化成我们所熟知的html、css、js文件。Vue官方给出了cil脚手架用来工程化组件开发!

  • 命令行创建Vue工程

    Vue create project-name
    
    • 构建时选择Vue2

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    • 构建完成后命令行进入到项目的根目录下运行如下指令启动项目

      npm run serve
      
    • 运行成功浏览器输入url:http://localhost:8080/)查看项目

      外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • Vue cil项目文件架构:Vue基础——vue-cil项目目录结构_一个vue cil-CSDN博客

十五、组件通信

  • 组件通信是Vue组件开发的重中之重,**掌握好各类组件通信是必要的!**所谓组件通信即组件之间相互传递数据,不同关系的组件传递数据的方式既不相同。

1.父子通信 props

  • 示例:

    • 父组件App.vue

      <template>
        <div id="app">
           <!-- 为子组件标签传递name属性且数据类型为字符串 -->
          <School :name="'老八'"></School>
        </div>
      </template>
      <script>
      import School from './components/School.vue'
      export default {
        name: 'App',
        components: {
          School
        }
      }
      </script>
      
    • 子组件School.vue

      <template>
        <div>
          <h1>学生信息</h1>
          <h2>学生姓名: <span v-text="name"></span></h2>
          <h2>学生性别: <span v-text="sex"></span></h2>
          <h2>学生年龄: <span v-text="studentAge"></span></h2>    
          <button @click="updateAge">点我给老八减年龄</button>
        </div>
      </template>
      <!-- 注意 不要修改props中的属性,必要修改时可将数据拷贝给data中的属性,改变data中的属性以实现效果 -->
      <script>
      export default {
        name:'School',
        data() {
          return {
            studentAge:this.age
          }
        },
        methods: {
          updateAge(){
            this.studentAge--
          }
        },
        // 第一种写法 简写
        // props:['name' , 'sex' , 'age']
      
        // 第二种写法 规定prop的数据类型
        // props:{
        //   name:String,
        //   sex:String,
        //   age:Number
        // }
        // 第三种写法 规定prop属性的数据类型、必要性、默认值
        props:{
          name:{
            type:String,
            required:true
          },
          sex:{
            type:String,
            default:'女'
          },
          age:{
            type:Number,
            default:88
          }
        }
      }
      </script>
      
  • props的使用

    • 如上示例似乎有些眼花缭乱?不必担心让我们的视角先来到子组件,我们看看子组件school需要什么数据,在v-text中需要三个数据,分别为name、sex、age,这些数据都需要由父组件传递给它,此时使用Vue提供的props便可以派上用场了!props配置属性的作用是接收由父组件传递给子组件的数据,这也意味着父组件仅仅只是传递了数据子组件并不能直接应用数据,而是需要在props配置属性中接收父组件传递的数据方可应用。

    • props配置属性接收父组件传递的数据有两种方式,数组式、对象式。

      • 数组式接收的方法比较简单,仅需在props属性的value部分添加一个字符串数组,这其中的字符串不可乱填,要与父组件传递数据时的key相同才可接收!

        // 第一种写法 简写
        props:['name' , 'sex' , 'age']
        
      • 对象式的配置显然有了更多的可选项,可以规定每个属性需要的数据类型、必要性及默认值,如果您仅需要规定数据类型可使用简写写法。

          // 简写写法 规定prop的数据类型
           props:{
             name:String,
             sex:String,
             age:Number
           }
          // 完整写法 规定prop属性的数据类型、必要性、默认值
          props:{
            name:{
              type:String,
              required:true
            },
            sex:{
              type:String,
              default:'女'
            },
            age:{
              type:Number,
              default:88
            }
        
  • 看了挺久还不知否父组件如何传值?实际上父组件想要传递数据仅需在应用组件的标签体添加需要传递的数据即可,就像这样:

    <template>
      <div id="app">
         <!-- 为子组件标签传递name属性且数据类型为字符串 -->
        <School :name="'老八'"></School>
      </div>
    </template>
    
    • 注意传递数据时key部分实则使用v-bind绑定的形式传递较为合适,无论你是否需要传递父组件内data中的数据,即使是固定的字符串!不采用v-bind形式传递仅能传递字符串形式的数据,而v-bind内传递的数据有明确的数据类型!
  • 还有一点也是极为重要的!**Vue不推荐开发者改变props内接受的数据!**子组件中操作props内接收的数据慎用v-model,**因为无形之间会改变传递来的元数据。**提示一下,当props传递的数据的数据类型为对象时,Vue无法判断开发者是否改变了对象内的属性、这也不会引起Vue的警告,但依然不推荐您这么做!

2.子父通信 props

  • 示例

    • 父组件App.vue

      <template>
        <div id="app">
          <!-- 将自身所拥有的函数传递给子组件,注意使用v-bind! -->
          <Son :givefatherData="getSonData"></Son>
        </div>
      </template>
      <script>
      import Son from './components/Son.vue'
      export default {
        name: 'App',
        components: {
          Son
        },
        methods: {
          getSonData(data) {
            console.log("父组件接收到了来自子组件的数据!数据为:", data);
          }
        }
      }
      </script>
      
    • 子组件Son.vue

      <template>
      	<!-- 将想要传递的数据写作形参传递给父组件 -->
        <button @click="givefatherData(name)">给父亲组件发送数据</button>
      </template>
      <script>
      export default {
        data() {
          return {
            name: 'son1'
          }
        },
        props: {
           // 声明接收该数据的数据类型为 函数
          givefatherData: Function
        }
      }
      </script>
      
  • 是的,子对父传递数据依然可以依赖props属性实现,且用法基本一致,唯一不同的是,父亲本次传递的数据的数据类型是函数,父组件可以将自身methods配置属性中的函数传递给子组件,子组件接收到后可以直接当做方法使用,在子组件调用方法时可将想传递给父组件的数据写作实参调用函数**,该函数会在父组件的定义处执行**,父组件的形参部分自然会收到由子组件传递的参数,如此便实现了子父通信。

3.子父通信 自定义事件实现

  • Vue支持开发者自定义事件,这也使得又增加了一种子父之间的通信方式,首先需要需要学习如何自定义事件!先看下面这个案例

    • 父组件

      <template>
        <div id="app">
          <!--  
          在标签中使用 '@自定义事件名="回调方法" ' 的形式为子组件定义方法,
              子组件需要调用 this.$emit('事件名' , ...prams)方法来触发事件并传递数据 
          -->
          <Son @definEvent="getSonData"></Son>
        </div>
      </template>
      <script>
      import Son from './components/Son.vue'
      export default {
        name: 'App',
        components: {
          Son
        },
        methods: {
          getSonData(data) {
            console.log("父组件接收到了来自子组件的数据!数据为:", data);
          }
        }
      }
      </script>
      
    • 子组件

      <template>
        <button @click="fun">给父亲组件发送数据</button>
      </template>
      <script>
      export default {
        data() {
          return {
            name: 'son1'
          }
        }, methods: {
          fun() {
            // 执行此方法时会触发自定义事件并传参
            this.$emit('definEvent', this.name)
          }
        }
      }
      </script>
      
  • 通过示例可以看到绑定事件的方法比较简单且同样具有传参的效果,值得注意的是,事件的定义需要在父组件进行,由父组件为子组件绑定事件,这样做正好可以为把事件触发的函数设置为父组件中存在的函数,这样子组件在触发时就会自动调用父组件中的方法,这与props通过传递函数来子父通信的方式类似,不是吗?

  • 在事件定义完成后子组件需要调用$emit(‘事件名’ , prams)方法来触发事件,事件名要与定义时一致!,在事件名之后填写你想要传递的数据!

  • 事件的定义还可以使用另一种方法!就是使用$on()方法来定义,不过这需要获得子组件的实例vc,我们要将事件定义在子组件身上!幸亏Vue提供了ref属性可以在模板解析时获取到子组件的vc实例。示例如下:

    <template>
      <div id="app">
         <!-- 添加了ref属性的子组件会在模板解析时将所在子组件的实例传递给父组件的$refs对象中 -->
        <Son2 ref="son"></Son2>
      </div>
    </template>
    <!-- 
        在标签中使用 'ref="组件实例名" ' ,当父组件挂载完毕时再给子组件绑定事件,使用 this.$refs.son2.$on('事件名' , this.回调函数名)的形式来绑定事件
     -->
    <script>
    import Son from './components/Son2.vue'
    export default {
      name: 'App',
      components: {
        Son
      },
      methods: {
        getSonData(data) {
          console.log("父组件接收到了来自子组件的数据!数据为:", data);
        }
      }
      // 在父组件挂载完毕时给子组件绑定事件
      mounted() {
        this.$refs.son.$on('sendSon2Name', this.getSonData)
      },
    }
    </script>
    
    
  • 当事件所在的vc实例被销毁时事件通常应当解绑!,Vue提供了$off(‘需解绑的事件名’)函数用于解绑数据。需要注意事件的解绑必须在事件所在的实例!这与定义事件时的情况是不同的!

    <template>
      <button @click="fun">给父亲组件发送数据</button>
    </template>
    <!--
    解除事件
      1. this.$off('事件名')  单个解绑
      2. this.$off(['事件名1' , '事件名2'])  批量解绑
      注意:解绑事件需要在事件所在的实例中进行!
    -->
    <script>
    export default {
      data() {
        return {
          name: 'son1'
        }
      }, methods: {
        fun() {
          // 执行此方法时会触发自定义事件并传参
          this.$emit('definEvent', this.name)
        }
      },
      // 在本vc被销毁前解绑事件    
      beforeDestroy(){
          this.$off('definEvent')
      }
    }
    </script>
    

4.任意组件间的通信 全局事件总线

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 全局事件总线的设计思路依然是利用事件触发会自动调用回调函数这一特点实现,通过上方的原型对象的指向图我们可以看出:无论是vm还是vc,他们的__proto__属性最终都会指向Vue的原型对象,这也意味着Vue原型对象上的任何属性任何方法都可以直接的被vc、vm访问到,我们可以利用这一特点,在Vue的原型对象身上挂载一个具有事件定义函数 o n ( ) 和事件触发函数 on()和事件触发函数 on()和事件触发函数emit()的对象,并由不同的组件进行事件定义与触发,那么就可以达到任意组件间通信的效果!

  • 全局事件总线实现

    • 根据刚刚的思路,我们首先要选择一个实例挂载到vm身上,在单文件组件开发的项目中,vm掌管所有组件,vm就再合适不过了!因此我们往Vue原型对象身上安装vm作为全局事件总线的承载对象

      new Vue({
        // 在数据代理 数据检测定义前安装全局事件总线vm
        beforeCreate() {
          Vue.prototype.$bus = this
        },
        render: h => h(App),
      }).$mount('#app')
      
    • 使用示例

      • 兄弟组件 component1.vue

        <template>
          <div>
            <button @click="send">给组件2发信息</button>
          </div>
        </template>
        
        <script>
        export default {
          data() {
            return {
              msg: 'hello,I’m component1!'
            }
          }, methods: {
            send() {
               // 发送数据只需要触发事件并传参即可
               // 哪个组件想要传参哪个组件触发事件
              this.$bus.$emit('sendMessage', this.msg)
            }
          }
        }
        </script>
        
      • 兄弟组件 component2

        <template></template>
        <script>
        export default {
        
          methods: {
            // 事件发生后的回调函数,注意接收参数
            getMessage(message) {
              console.log("我是组件2,我收到来自信息:", message)
            }
          },
          mounted() {
             // 哪个组件需要获取数据事件定义就在哪个组件文件中
            this.$bus.$on('sendMessage', this.getMessage)
          }
        }
        </script>
        
        

5.任意组件间的通信 消息订阅与发布

  • 消息订阅与发布的原理与全局事件总线神似,所以Vue项目中通常会选择全局事件总线来实现任意组建件的通信,但消息订阅与发布的方式也要掌握!

  • 消息订阅与发布的使用

    • npm安装第三库pubsub-js

      npm i pubsub-js
      
    • 示例

      • 兄弟组件1 component1.vue

        <template>
          <div>
            <button @click="send">给组件2发信息</button>
          </div>
        </template>
        <script>
        // 引入库
        import pubsub from 'pubsub-js'
        export default {
          data() {
            return {
              msg: 'hello,I’m component1!'
            }
          }, methods: {
            send() {
              // 发布消息,谁发送数据谁发布
              pubsub.publish('sendMessage', this.msg)
            }
          },
        }
        </script>
        
      • 兄弟组件2 component2.vue

        <template></template>
        
        <script>
        // 引入库
        import pubsub from 'pubsub-js'
        export default {
          methods: {
            // 注意,使用消息订阅与发布的方式接收数据,第一个形参默认为消息名,第二个形参开始才是数据,建议使用下划线占位
            getMessage(_, message) {
              console.log("我是组件2,我收到来自信息:", message)
            }
          },
          mounted() {
            // 订阅消息 谁需要消息谁订阅
            this.pid = pubsub.subscribe('sendMessage', this.getMessage)
          }, beforeDestroy() {
            // 实例销毁前取消订阅
            pubsub.unsubscribe(this.pid)
          }
        }
        </script>
        

十六、过渡与动画

  • Vue在插入、更新或者移除DOM时,提供了多种不同的方式应用过渡效果。**被应用过渡或动画的元素会在不同时期添加或删除固定的类名以达到自动管理过渡与动画的效果。**无需操作DOM进行手动添加类名

  • Vue中插入与删除的过程中会添加的类名

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 动画的使用

    • 示例

      <template>
        <div>
          <button @click="next">click me show</button>
          <!-- 想让Vue管理过渡与动画就必须使用transition标签包裹起来 appear代表第一次挂载dom就应用动画 -->
          <transition appear name="ani1">
            <h1 v-show="isFlag">动画实现 </h1>
          </transition>
      
        </div>
      </template>
      
      <script>
      export default {
        data() {
          return {
            isFlag: true
          }
        },
        methods: {
          next() {
            this.isFlag = !this.isFlag
          }
        },
      }
      </script>
      
      <style >
      h1 {
        background-color: skyblue;
      }
      /* 应用进入时动画 */
      .ani1-enter-active {
        animation: enter 0.5s;
      }
      /* 离开时仅将动画反转即可 */
      .ani1-leave-active {
        animation: enter 0.5s reverse;
      }
      /* 定义进入时动画过程 */
      @keyframes enter {
        0% {
          transform: translateX(-100%);
        }
      	
        100% {
          transform: translateX(0);
        }
      }
      </style>
      
  • 过渡的使用

    • 示例

      <template>
        <div>
          <button @click="show">click me show</button>
          <!-- appear 表示挂载页面时就刷新页面 -->
          <transition name="trans1" appear>
            <h1 v-show="isFlag">过渡实现</h1>
          </transition>
        </div>
      </template>
      
      <script>
      export default {
        data() {
          return {
            isFlag: true
          }
        },
        methods: {
          show() {
            this.isFlag = !this.isFlag
          }
        },
      }
      </script>
      
      <style scoped>
      h1 {
        background-color: orange;
      }
      /* 元素进入的起点、元素离开时需要到的位置 */
      .trans1-enter,
      .trans1-leave-to {
        transform: translateX(-100%);
      }
      /* 元素添加与元素删除的过程中应用过渡时间属性 */
      .trans1-enter-active,
      .trans1-leave-active {
        transition-duration: 0.5s;
      }
      /* 元素进入时、离开时起点的位置 */
      .trans1-enter-to,
      .trans1-leave {
        transform: translateX(0);
      }
      </style>
      
  • 第三方库的使用

    • npm安装第三方库

      npm i animate.css --save  
      
    • animate.css官网:动画.css |CSS 动画的跨浏览器库。 (animate.style)

    • 使用

      <template>
        <div>
          <button @click="show">click me show</button>
          <!-- name要添加第三方库给出的类名
          enter-active-class="animate__flip"        元素添加时的动画
          leave-active-class="animate__fadeOutUp"   元素删除时的动画 
          -->
          <transition name="animate__animated animate__bounce" appear enter-active-class="animate__flip"
            leave-active-class="animate__fadeOutUp">
            <h1 v-show="isFlag">第三方库</h1>
          </transition>
        </div>
      </template>
      
      <script>
      import 'animate.css'
      export default {
        data() {
          return {
            isFlag: true
          }
        },
        methods: {
          show() {
            this.isFlag = !this.isFlag
          }
        },
      }
      </script>
      
      <style scoped>
      h1 {
        background-color: rgb(187, 69, 14);
      }
      </style>
      

十七、插槽

1.插槽的使用场景

  • 当页面中需要多个HTML结构不同的子组件时就可以使用插槽来解决问题,这样您就无需定义多个组件,当然,如果绝大部分HTML结构是不同的场景下还是定义多个组件比较好!插槽适用于小部分HTML结构不同的场景使用。插槽最大的作用是父子组件能传递HTML元素数据。

2.几种不同的插槽的使用

  • 默认插槽

    • 示例

      • 父组件App.vue

        <template>
          <div id="app">
            <!-- 默认插槽 子组件双标签中的全部内容都会被插入到子组件中的slot标签内-->
            <Son title="游戏">
              <ul>
                <li v-for="game in games" :key="game" v-text="game"></li>
              </ul>
            </Son>
          </div>
        </template>
        <script>
        import Son from './components/Slot.vue'
        export default {
          name: 'App',
          components: {
            Son
          },
          data() {
            return {
              games: ['红色警戒', '魔兽世界', '星际争霸', '魔兽争霸', '炉石传说']
            }
          },
        }
        </script>
        <style scoped>
        #app {
          display: flex;
          justify-content: center;
          align-items: center;
        }
        li {
          text-align: center;
          font-size: 16px;
        }
        a {
          margin: 0 10px;
        }
        </style>
        
      • 子组件 Son.vue

        <template>
          <div id="item">
            <h2>{{ title }}分区</h2>
          	<!-- 
        	父组件书写在该组件的双标签内的全部html结构都会被插入到slot标签的占位处
        	如果存在多个slot标签则html结构会被渲染多个
        	-->    
            <slot></slot>
          </div>
        </template>
        <script>
        export default {
          props: ['title'],
        }
        </script>
        <style scoped>
        #item {
          width: 200px;
          height: 300px;
          background-color: aqua;
          margin: 0 20px;
        }
        h2 {
          text-align: center;
          font-size: 30px;
        }
        </style>
        
    • 具名插槽

      • 见名知意,具名插槽指每个slot标签都具有name属性,标注了不同name值的根标签会被渲染到不同的插槽中。

      • 示例

        • 父组件App.vue

          <template>
            <div id="app">
              <!-- 具名插槽 -->
              <Son title="游戏">
                 <!-- 这部分HTML会被渲染到name属性为content的slot标签的位置 -->
                <ol slot="content">
                  <li v-for="game in games" :key="game" v-text="game"></li>
                </ol>
                 <!-- 这部分HTML会被渲染到name属性为foot的slot标签的位置 -->
                 <!-- template标签可以使用新指令 v-slot -->
                <template slot="foot">
                  <a href="">暴雪游戏</a>
                  <a href="">通配符游戏</a>
                </template>
              </Son>
            </div>
          </template>
          <script>
          import Son from './components/Slot.vue'
          export default {
            name: 'App',
            components: {
              Son
            },
            data() {
              return {
                games: ['红色警戒', '魔兽世界', '星际争霸', '魔兽争霸', '炉石传说']
              }
            },
          }
          </script>
          <style scoped>
          #app {
            display: flex;
            justify-content: center;
            align-items: center;
          }
          li {
            text-align: center;
            font-size: 16px;
          }
          a {
            margin: 0 10px;
          }
          </style>
          
        • 子组件 Son.vue

          <template>
            <div id="item">
              <h2>{{ title }}分区</h2>
               <!-- 此处slot插槽为slot属性为content的根元素及其子元素占位 -->
              <slot name="content"></slot>
               <!-- 此处slot插槽为slot属性为foot的根元素及其子元素占位 -->
              <slot name="foot"></slot>
            </div>
          </template>
          <script>
          export default {
            props: ['title'],
          }
          </script>
          <style scoped>
          #item {
            width: 200px;
            height: 300px;
            background-color: aqua;
            margin: 0 20px;
          }
          h2 {
            text-align: center;
            font-size: 30px;
          }
          </style>
          
      • 作用域插槽

        • 作用域插槽的使用场景是父组件内想要传递给子组件的HTML元素渲染时需要使用子组件中的data数据,父组件中的所有HTML都是在父组件中编译完成的,编译完成后才传入子组件!所有在组件双标签内的HTML不能直接使用子组件中的data数据。作用域插槽就解决了这一问题。**它旨在为父组件传递编译自身HTML元素时所需的数据!**这句话可能有些许难以理解。

        • 示例

          • 父组件 App.vue

            <template>
              <div id="app">
                <!-- 作用域插槽 -->
                <Son title="游戏">
                  <!-- 注意格式! slot-scope="{key}" -->
                  <template slot-scope="{gameList}">
                  <!-- 此处需要使用gameList数据 使用slot-scope 接收的gameList -->
                    <ul>
                      <h3 v-for="game in gameList" :key="game">{{ game }}</h3>
                    </ul>
                  </template>
                </Son>
              </div>
            </template>
            <script>
            import Son from './components/Slot.vue'
            export default {
              name: 'App',
              components: {
                Son
              }
            }
            </script>
            <style scoped>
            #app {
              display: flex;
              justify-content: center;
              align-items: center;
            }
            li {
              text-align: center;
              font-size: 16px;
            }
            a {
              margin: 0 10px;
            }
            </style>
            
          • 子组件 Son.vue

            <template>
              <div id="item">
                <h2>{{ title }}分区</h2>
                <!-- 
            	为父组件编译插槽占位的HTML时提供数据
            	这种传递方式类似于父子通信的 props属性
            	-->
                <slot :gameList="gameList"></slot>
              </div>
            </template>
            <script>
            export default {
              props: ['title'],
              data() {
                return {
                  gameList: ['红色警戒', '魔兽世界', '星际争霸', '魔兽争霸', '炉石传说']
                }
              },
            }
            </script>
            <style scoped>
            #item {
              width: 200px;
              height: 300px;
              background-color: aqua;
              margin: 0 20px;
            }
            h2 {
              text-align: center;
              font-size: 30px;
            }
            </style>
            

3.使用插槽要注意:

  • v-onv-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header
  • 父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

十八、Vuex

Vuex官方文档:Vuex 是什么? | Vuex (vuejs.org)

1.什么是Vuex?

  • 这里首先要引用一下官方的介绍:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension (opens new window),提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

  • 个人理解:Vuex的职责是对共享数据的同一封装和管理。

2.Vuex的工作流程

  • Vuex的工作流程大致可分三个模块,分别为Actions、Mutations、State,这三个模块进行有机统一共同完成了对共享数据的分装与操作,对于三层结构,我认为有点类似于JavaWeb后端项目中的分层思想,如果你也是java后端开发我相信你也会这么认为的!当然,这不是Java所独创的,而是整个软件开发世界共同的智慧结晶。
    • Actions:类似于业务层service,如果共享数据运算前需要进行一定的逻辑判断,**相关的逻辑判断部分代码就需要书写在Actions中。**就像后端中的controller也能做到直接与数据库交互,但为了解耦和方便维护,我们不会那么做。而是各层各司其职。值得注意的是,向Actions中传递了操作后并不会自动的进行下一步。而是需要手动commit。
    • Mutations:**类似于Mapper层,**该处可以直接接过Actions处理后的结果直接与State中的共享数据进行运算。
    • State:**类似于数据库。**用于定义和存储共享数据。
  • 还有一点是极为重要的!三者并不是直接在Vuex的管理下运行,而是在Vuex下的store对象中

3.Vuex的安装

在Vuex正常使用前,是需要进行一定的配置Vuex才能正常运行。

  • 引入(Vue2对应的Vuex是Vuex3版本)

    npm i vuex@3 
    
  • 在src下创建store\index.js文件,用于配置Vuex:

    // Vuex配置
    import Vue from 'vue' // 引入Vue原型对象
    import Vuex from 'vuex' // 引入Vuex
    Vue.use(Vuex) // 此处使用Vue插件推荐在此处进行而不是在main.js中执行,import属性会优于其他语句执行
    // Actions配置对象
    const actions = {
      // 配置属性,属性为函数,例如:
      addObb(store, value) {
        if (store.state.sum % 2) {
          store.commit('ADD', value)
        }
      }
    }
    
    // Mutations配置对象
    const mutations = {
      // 配置属性,属性为函数,例如:
      ADD(context, value) {
        context.sum += value
      }
    }
    
    // State配置对象
    const state = {
      // 配置属性,属性为需要共享的数据
      sum: 0
    }
    
    // 此处为计算属性,就像computed中的属性作用类似
    const getters = {
      bigNumber(state) {
        return state.sum * 10
      }
    }
    
    // 此处需要注意不能直接new Vuex() , Vuex下的Store才是管理三者的对象
    export default new Vuex.Store({
      actions,
      mutations,
      state
    })
    
  • 在main.js中引入该js文件并安装至vm上

    import Vue from 'vue'
    import App from './App.vue'
    // 引入配置好的store对象
    import store from './store'
    Vue.config.productionTip = false
    
    new Vue({
      render: h => h(App),
      store // 安装到vm上
    }).$mount('#app')
    

4.Vuex的使用

  • 案例:对共享数据sum的操作

    • store\index.js

      // Vuex配置
      import Vue from 'vue'
      import Vuex from 'vuex'
      Vue.use(Vuex)
      const actions = {
        addObb(store, value) {
          if (store.state.sum % 2) {
            store.commit('ADD', value)
          }
        },
        waitAdd(store, value) {
      
          setTimeout(() => {
            store.commit('ADD', value)
          }, 1000)
      
        },
      }
      const mutations = {
        ADD(context, value) {
          context.sum += value
        },
        MUL(context, value) {
          context.sum -= value
        },
      }
      const state = {
        sum: 0,
        school: '尚硅谷',
        subject: 'Vue'
      }
      
      // 此处为计算属性,就像computed中的属性作用类似
      const getters = {
        bigNumber(state) {
          return state.sum * 10
        }
      }
      export default new Vuex.Store({
        actions,
        mutations,
        state,
        getters
      })
      
    • component1.vue

      <template>
        <div>
          <h1>当前求和为:{{ sum }}</h1>
          <h1>当前求和数10倍为:{{ big }}</h1>
          <select name="" id="" v-model="step">
            <option :value="1">1</option>
            <option :value="2">2</option>
            <option :value="3">3</option>
          </select>
           <!-- 由于使用了简写,函数使用时需要主动传递值 -->
          <button @click="add(step)">+</button>
          <button @click="mul(step)">-</button>
          <button @click="addObb(step)">当前求和为奇数再加</button>
          <button @click="waitAdd(step)">等一等再加</button>
          <h1>我在{{ school }}学习{{ subject }}</h1>
        </div>
      </template>
      
      <script>
      // 需要简写State中的属性就引入mapState // 需要简写Actions中的方法就引入mapMutations
      import { mapMutations, mapState } from 'vuex'
      // 需要简写getter中的计算属性就引入mapGetters
      import { mapGetters } from 'vuex'
      // 需要简写Actions中的方法就引入mapActions
      import { mapActions } from 'vuex'
      
      export default {
        data() {
          return {
            step: 1
          }
        },
        computed: {
          // 简写方式
          ...mapState(['sum', 'school', 'subject']),
          // 对象式简写
          ...mapGetters({ big: 'bigNumber' }),
        },
        methods: {
          // 注意简写后要在函数引入处为函数传参,否则默认传递事件对象
          ...mapMutations({ add: 'ADD', mul: 'MUL' }),
          ...mapActions(['addObb', 'waitAdd']),
        },
      }
      </script>
      
      <style>
      button {
        margin-left: 10px;
        cursor: pointer;
      }
      </style>
      

5.Vuex模块化

  • 在大型项目中,共享数据通常非常庞大,这会造成大量不同类的函数与数据冗余在一起,非常不便于区分,此时需要引入模块化

  • 示例:

    • store\index.js

      // Vuex配置
      import Vue from 'vue'
      import Vuex from 'vuex'
      Vue.use(Vuex)
      
      // 人员相关
      const personAbout = {
        //开启命名空间才能使用模块名分类
        namespaced: true,
        actions: {
      
        },
        mutations: {
          addPerson(context, value) {
            context.personList.unshift(value)
          }
        },
        state: {
          personList: []
        }
      }
      
      // sum相关
      const sumOptions = {
        //开启命名空间才能使用模块名分类
        namespaced: true,
        actions: {
          addObb(store, value) {
            if (store.state.sum % 2) {
              store.commit('ADD', value)
            }
          },
          waitAdd(store, value) {
      
            setTimeout(() => {
              store.commit('ADD', value)
            }, 1000)
          },
      
        },
        mutations: {
          ADD(context, value) {
            context.sum += value
          },
          MUL(context, value) {
            context.sum -= value
          },
        },
        getters: {
          bigNumber(state) {
            return state.sum * 10
          }
        },
        state: {
          school: '尚硅谷',
          subject: '前端',
          sum: 0
        }
      }
      // 暴露并创建Vuex的stroe实例
      export default new Vuex.Store({
        modules: {
          // 引入每个模块的配置
          sumAbout: sumOptions,
          personAbout: personAbout
        }
      
      })
      
    • component1.vue

      <template>
        <div>
          <h1>当前求和为:{{ sum }}</h1>
          <h1>当前求和数10倍为:{{ big }}</h1>
          <select name="" id="" v-model="step">
            <option :value="1">1</option>
      
            <option :value="2">2</option>
            <option :value="3">3</option>
          </select>
          <button @click="add(step)">+</button>
          <button @click="mul(step)">-</button>
          <button @click="addObb(step)">当前求和为奇数再加</button>
          <button @click="waitAdd(step)">等一等再加</button>
          <h1>我在{{ school }}学习{{ subject }}</h1>
          <h2 style="color: red;">下方组件的总人数有{{ personList.length }}人</h2>
        </div>
      </template>
      
      <script>
      // 引入需要映射属性的映射器
      import { mapMutations, mapState, mapActions, mapGetters } from 'vuex'
      export default {
        data() {
          return {
            step: 1
          }
        },
        computed: {
          // 因为使用了模块化,所以需要在映射时需要额外填写一个配置项指明该属性在哪个模块中
          ...mapState('sumAbout', ['sum', 'school', 'subject']),
          ...mapState('personAbout', ['personList']),
          // 对象式简写
          ...mapGetters('sumAbout', { big: 'bigNumber' }),
        },
        methods: {
          // 注意简写后要在函数引入处为函数传参,否则默认传递事件对象
          ...mapMutations('sumAbout', { add: 'ADD', mul: 'MUL' }),
          ...mapActions('sumAbout', ['addObb', 'waitAdd']),
        },
      }
      </script>
      <style>
      button {
        margin-left: 10px;
        cursor: pointer;
      }
      </style>
      
    • component2.vue

      <template>
        <div>
          <h1>学生数量</h1>
          <h2 style="color: red;">上方组件的求和为:{{ sum }}</h2>
          <input type="text" placeholder="添加人员" @keyup.enter="save" v-model="name">
          <h2 v-for="person in personList" :key="person.id">{{ person.name }}</h2>
        </div>
      </template>
      
      <script>
      import { nanoid } from 'nanoid'
      import { mapState, mapMutations } from 'vuex'
      export default {
        data() {
          return {
            name: ''
          }
        },
        computed: {
          ...mapState('personAbout', ['personList']),
          ...mapState('sumAbout', ['sum'])
        },
        methods: {
          ...mapMutations('personAbout', ['addPerson']),
          save() {
            this.addPerson({ id: nanoid(), name: this.name })
            this.name = ''
          }
        },
      }
      </script>
      <style></style>
      
  • 使用模块化就是对原有的各项配置属性进行封装到一个对象内,多个对象构成了多个模块,再通过modules配置项装配到Vuex中。

  • 需要注意的是使用模块化时需要在Vuex的modules中的每个模块中开启命名空间namespaced:true才能正常通过模块名来引用对应模块的数据。

十九、Vue-Router

Vue Router官方文档:安装 | Vue Router (vuejs.org)

Vue Router是官方给出的路由管理插件,使用Vue Router可以轻松构建大型SPA应用,开发者只需要关注处理路径与组件之间的映射关系,路由的切换由Vue来接管。

1.Vue Router的使用

  • 安装Vue Router(vue-Router3对应Vue2)

    npm i vue-router@3
    
  • 与其他官方插件一致,使用Vue Router需要调用Vue原型对象的use()方法安装Vue Router,同时还需要将配制好的Vue Router实例安装到vm实例身上。

  • main.js

    import Vue from 'vue'
    import App from './App.vue'
    // 引入Vue Router
    import VueRouter from 'vue-router'
    // 引入配置好的Vue Router实例
    import router from './router'
    
    Vue.config.productionTip = false
    Vue.use(VueRouter) // 使用VueRouter插件
    
    new Vue({
      render: h => h(App),
      router   // 在使用了VueRouter后可传入router配置项
    }).$mount('#app')
    
  • router\index.js

    import Router from 'vue-router'
    // 引入需要路由管理的组件
    import About from '../pages/About.vue'
    import Home from '../pages/Home.vue'
    
    // 创建并暴露一个路由器实例
    export default new Router({
      // 路由配置项
      routes: [
        {
          path: '/about',
          component: About
        },
        {
          path: '/home',
          component: Home
        }
      ]
    })
    
  • 至此我们已经将Vue Router配置好并安装至vm了,Vue Router的用法比较简单,在需要有路由切换功能的a标签写成如下形式

    <div class="list-group">
        <!-- 
    		router-link 标签最终会编译成a标签
    		active-class 表示该组件激活时控制标签的样式类
    		to 表示需要切换至什么组件的path值
    	-->
        <router-link class="list-group-item" active-class="active" to="/about">About</router-link>
        <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
    </div>
    
  • 以上仅仅是配置了组件的切换管理,还需要指明路由切换后组件出现的位置,就像slot一样:

    <div class="panel-body">
        <!-- 设置路由后需要展示的位置 -->
        <router-view></router-view>
    </div>
    
  • 注意,Vue中被切换的路由是经历了创建和销毁过程的,通过生命周期的钩子函数可以检测到!

  • 每一个组件上都有 r o u t e 和 route和 routerouter属性, r o u t e 是每个组件独有的,具有所在组件的路由信息,但所有组件的 route是每个组件独有的,具有所在组件的路由信息,但所有组件的 route是每个组件独有的,具有所在组件的路由信息,但所有组件的router属性都是同一个3对象。

2.嵌套路由

  • 嵌套路由也称为多级路由,多级路由与一级路由的写法类似。

  • 示例

    • router/index.js

      import Router from 'vue-router'
      import About from '../pages/About.vue'
      import Home from '../pages/Home.vue'
      import Message from '../pages/Message.vue'
      import News from '../pages/News.vue'
      
      
      // 创建并暴露一个路由器实例
      export default new Router({
        routes: [
          {
            path: '/about',
            component: About
          },
          {
            path: '/home',
            component: Home,
             //多级路由的配置需要写在一级路由中的children配置属性中,配置类型为数据,同样需要指定路径和组件,需要注意的是二级路由及以上的path属性不能写‘/’
            children: [
              {
                path: 'message', // 不要写成 '/message'
                component: Message
              },
              {
                path: 'news',
                component: News
              },
            ]
          }
        ]
      })
      
    • Home.vue

      <template>
        <div>
          <h2>我是Home的内容</h2>
          <ul class="nav nav-tabs">
            <li>
              <!-- 这里要注意to属性中要写全路径才是正确的路径 -->
              <router-link class="list-group-item " active-class="active" to="/home/news">News</router-link>
           
            </li>
            <li>
              <router-link class="list-group-item " active-class="active" to="/home/message">Message</router-link>
            </li>
          </ul>
          <router-view></router-view>
        </div>
      </template>
      <script>
      import Message from './Message'
      export default {
        components: {
          Message
        },
        name: 'Home'
      }
      </script>
      

3.query传参

  • 在展示子级路由时,子级路由中页面的数据往往需要依赖父级路由传递的参数 ,此时便需要路由传参,query传参与url的query传参规则一致。以下是query传参的示例:

  • 示例

    • 父级路由组件 Message.vue

      <template>
        <div>
      
          <ul>
            <li v-for="m in list" :key="m.id">
              <!-- query传参 1.字符串写法 -->
              <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{ m.title }}</router-link>&nbsp;&nbsp; -->
                
              <!-- query传参 2.对象写法 推荐-->
              <router-link :to="{
                path: '/home/message/detail',
                query: {
                  id: m.id,
                  title: m.title
                }
      
              }">{{ m.title }}</router-link>&nbsp;&nbsp;
            </li>
          </ul>
          <hr>
          <!-- 组件展示的位置 -->
          <router-view></router-view>
        </div>
      </template>
      
      <script>
      export default {
        name: 'Message',
        data() {
          return {
            list: [
              { id: '001', title: '消息1' },
              { id: '002', title: '消息2' },
              { id: '003', title: '消息3' },
            ]
          }
        },
      }
      </script>
      
    • 子级路由 Detail.vue

      <template>
        <ul>
          <!-- 通过query传参方式传递来的参数会挂载到$route对象的query属性上 -->
          <li>id:{{ $route.query.id }}</li>
          <li>标题:{{ $route.query.title }}</li>
        </ul>
      </template>
      
      <script>
      export default {
      
      }
      </script>
      
      <style></style>
      

4.具名路由

  • 路由层级嵌套过多会造成书写繁杂的情况,具名路由可以有效解决此问题,通过给路由配置唯一标识的name属性可以直接给组件定位,不必在书写全路径名,但有一点需要注意!使用name属性定位组件的方式必须使用对象式配置

  • 示例:

    • 路由配置文件 router\index.js

      import Router from 'vue-router'
      import About from '../pages/About.vue'
      import Home from '../pages/Home.vue'
      import Message from '../pages/Message.vue'
      import News from '../pages/News.vue'
      import Detail from '../pages/Detail.vue'
      
      
      // 创建并暴露一个路由器实例
      export default new Router({
        routes: [
          {
            path: '/about',
            component: About
          },
          {
            path: '/home',
            component: Home,
            children: [
              {
                path: 'message',
                component: Message,
                children: [
                  {
                    // 此处配置路由组件的name属性
                    name:'detail',
                    path: 'detail',
                    component: Detail
                  }
      
                ]
              },
              {
                path: 'news',
                component: News
              },
            ]
          }
        ]
      })
      
    • 父级组件Message.vue

      <template>
        <div>
      
          <ul>
            <li v-for="m in list" :key="m.id">
              <!-- query传参 2.对象写法 推荐-->
              <router-link :to="{
                // 使用name属性代替path属性 注意使用name属性必须使用对象式配置
                name: 'detail',
                query: {
                  id: m.id,
                  title: m.title
                }
              }">{{ m.title }}</router-link>&nbsp;&nbsp;
            </li>
          </ul>
          <hr>
          <!-- 组件展示的位置 -->
          <router-view></router-view>
        </div>
      </template>
      
      <script>
      export default {
        name: 'Message',
        data() {
          return {
            list: [
              { id: '001', title: '消息1' },
              { id: '002', title: '消息2' },
              { id: '003', title: '消息3' },
            ]
          }
        },
      }
      </script>
      
      • 一级路由还是使用普通的字符串配置方式比较方便!

5.params传参

  • 前面我们介绍了路由的query传参方式,除此以外还有params传参方式。

  • 示例:

    • 路由配置文件 router/index.js

      import Router from 'vue-router'
      import About from '../pages/About.vue'
      import Home from '../pages/Home.vue'
      import Message from '../pages/Message.vue'
      import News from '../pages/News.vue'
      import Detail from '../pages/Detail.vue'
      
      // 创建并暴露一个路由器实例
      export default new Router({
        routes: [
          {
            path: '/about',
            component: About
          },
          {
            path: '/home',
            component: Home,
            children: [
              {
                path: 'message',
                component: Message,
                children: [
                  {
                    // 此处配置路由组件的name属性
                    name: 'detail',
                    path: 'detail/:id/:title', //此处声明detail组件接收params时的key
                    component: Detail
                  }
      
                ]
              },
              {
                path: 'news',
                component: News
              },
            ]
          }
        ]
      })
      
    • 父级组件 Message.vue

      <template>
        <div>
      
          <ul>
            <li v-for="m in list" :key="m.id">
              <!-- params传参 1.字符串写法-->
              <!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{ m.title }}</router-link>&nbsp;&nbsp; -->
      
              <!-- params传参 2.对象写法 推荐-->
              <router-link :to="{
                // 1. 使用params传参仅需把原本的query配置项的名字换成params
                // 2. 要注意使用对象式配置params传参时,不能使用path属性定位组件,必须使用name属性定位
                name: 'detail',
                params: {
                  id: m.id,
                  title: m.title
                }
      
              }">{{ m.title }}</router-link>&nbsp;&nbsp;
            </li>
          </ul>
          <hr>
          <!-- 组件展示的位置 -->
          <router-view></router-view>
        </div>
      </template>
      
      <script>
      export default {
        name: 'Message',
        data() {
          return {
            list: [
              { id: '001', title: '消息1' },
              { id: '002', title: '消息2' },
              { id: '003', title: '消息3' },
            ]
          }
        },
      }
      </script>
      
      
    • 子级组件Detail.vue

      <template>
        <ul>
          <!-- 通过params传参方式传递来的参数会挂载到$route对象的params属性上 -->
          <li>id:{{ $route.params.id }}</li>
          <li>标题:{{ $route.params.title }}</li>
        </ul>
      </template>
      
      <script>
      export default {
      
      }
      </script>
      
      <style></style>
      

6.props配置

  • 不论是params或是query传参的方式在取数据时都需要$route.params.title这种繁琐的操作,props配置属性可以解决这一问题,使得代码变得简洁工整。

  • 示例

    • 路由配置文件 router/index.js

      import Router from 'vue-router'
      import About from '../pages/About.vue'
      import Home from '../pages/Home.vue'
      import Message from '../pages/Message.vue'
      import News from '../pages/News.vue'
      import Detail from '../pages/Detail.vue'
      
      
      // 创建并暴露一个路由器实例
      export default new Router({
        routes: [
          {
            path: '/about',
            component: About
          },
          {
            path: '/home',
            component: Home,
            children: [
              {
                path: 'message',
                component: Message,
                children: [
                  {
                    // 此处配置路由组件的name属性
                    name: 'detail',
                    path: 'detail/:id/:title',
                    component: Detail,
                    // 1.props属性第一种配置方法 少用,该方法仅能传递固定参数
                    // props: { id: 1, title: '固定标题' }
      
                    // 2.props属性第二种配置方法 
                    //该方法会将传递的params参数转化成props属性 但是不能将query参数转化!
                    // props: true
      
                    // 3.props属性的第三种配置方式 推荐
                    // 为props配置一个回调函数,vue在加载子组件时会执行此回调函数并传递该页面的$route对象
                    // 该方式同时兼容params传参和query传参,两种配置方式均可
      
                    //传统写法
                    /* props($route) {
                      return {
                        id: $route.params.id,
                        title: $route.params.title
                      }
                    } */
      
                    // 解构赋值写法
                    props({ params }) {
                      return {
                        id: params.id,
                        title: params.title
                      }
                    }
                  }
                ]
              },
              {
                path: 'news',
                component: News
              },
            ]
          }
        ]
      })
      
    • 子级组件 Detail.vue

      <template>
        <ul>
          <!-- 通过query传参方式传递来的参数会挂载到$route对象的query属性上 -->
          <li>id:{{ id }}</li>
          <li>标题:{{ title }}</li>
        </ul>
      </template>
      
      <script>
      export default {
        props: ['id', 'title'] // 在此接收父级组件传递来的param 或是 query参数
      
      }
      </script>
      
      <style></style>
      

7.<router-link>的replace属性

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

  • 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换历史记录。路由跳转时默认为push。

  • 开启了replace模式浏览器无法后退。

  • 开启replace模式:

    <router-link replace class="list-group-item" active-class="active" to="/home">Home</router-link>
    

8.编程式路由导航

  • 在实际的开发中,<router-link>标签往往不能满足我们的需求,因为该标签最终会被解析成a标签,而有时我们需要其他元素…使用编程式路由导航可以完美替代<router-link>标签>。

  • 示例:

    • 父级组件 Message.vue

      <template>
        <div>
      
          <ul>
            <li v-for="m in list" :key="m.id">
              <button @click="pushShow(m)">push查看:{{ m.title }}</button>
              <button @click="replaceShow(m)">replace查看:{{ m.title }}</button>
            </li>
          </ul>
          <hr>
          <!-- 组件展示的位置 -->
          <router-view></router-view>
        </div>
      </template>
      
      <script>
      export default {
        name: 'Message',
        data() {
          return {
            list: [
              { id: '001', title: '消息1' },
              { id: '002', title: '消息2' },
              { id: '003', title: '消息3' },
            ]
          }
        },
        methods: {
          pushShow(m) {
            /*
              路由器$router上存在push和replace方法,调用这两个方法并传递配置参数即可实现路由跳转
              除此以外$router上还有 back() forward() go() 方法等控制浏览器历史记录跳转的方法
            */
            this.$router.push({
              name: 'detail',
              params: {
                id: m.id,
                title: m.title
              }
            })
          },
          replaceShow(m) {
            this.$router.replace({
              name: 'detail',
              params: {
                id: m.id,
                title: m.title
              }
            })
          }
        },
      }
      </script>
      

9.缓存路由组件

  • 当页面出现某些表单类元素且希望在路由切换后仍然能够保存已输入的值,我们就需要缓存路由组件来保护这个组件不被销毁,**因为vue Router的路由切换默认是经过销毁和创建过程的!**在Vue Router中,通过`标签可以保持组件在切换时不被销毁。

  • 示例

    • 父组件 Home.vue

      <template>
        <div>
          <h2>我是Home的内容</h2>
          <ul class="nav nav-tabs">
            <li>
              <router-link class="list-group-item " active-class="active" to="/home/news">News</router-link>
            </li>
            <li>
              <router-link class="list-group-item " active-class="active" to="/home/message">Message</router-link>
            </li>
          </ul>
            <!--
      		1.此处如果不写include则默认该组建下所有子孙组件都缓存
      		2.include的value部分需要填写组件的name值
      ·		3.如果需要缓存多个组件则写成数组的形式
      			<keep-alive :include="['News','Mwssage']" 
            			<router-view></router-view>
          		</keep-alive>
      	-->
          <keep-alive include="News">
            <router-view></router-view>
          </keep-alive>
            
        </div>
      </template>
      

10.路由组件所独有的两个生命周期钩子

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

11.路由守卫

  • 什么是路由守卫?

    对路由进行权限控制

  • 全局守卫

    • 示例 router/index.js

      import Router from 'vue-router'
      import About from '../pages/About.vue'
      import Home from '../pages/Home.vue'
      import Message from '../pages/Message.vue'
      import News from '../pages/News.vue'
      import Detail from '../pages/Detail.vue'
      
      
      // 创建并暴露一个路由器实例
      const router = new Router({
        routes: [
          {
            path: '/about',
            component: About,
            meta: { title: '关于' },
          },
          {
            path: '/home',
            component: Home,
            meta: { title: '主页' },
            children: [
              {
                path: 'message',
                component: Message,
                meta: { isCheck: true },
                children: [
                  {
                    name: 'detail',
                    path: 'detail/:id/:title',
                    component: Detail,
                    // 解构赋值写法
                    props({ params }) {
                      return {
                        id: params.id,
                        title: params.title
                      }
                    }
      
      
                  }
                ]
              },
              {
                path: 'news',
                component: News,
                meta: { isCheck: true },
              },
            ]
          }
        ]
      })
      
      // 全局前置路由守卫 在初次加载页面前 路由切换前执行
      router.beforeEach((to, from, next) => {
        /*
          1.  to对象存储需要切换到的组件的信息
              from对线存储切换前来自哪个组件的信息
              next方法是执行切换组件的方法
          2.to、from都有个meta属性,该属性为开发者存储组件的定制属性
        */
        if (to.meta.isCheck) { // 判断要去的路由是否需要进行权限控制
          if (localStorage.getItem('school') === 'atguigu') { // 权限控制的具体规则
            next() // 放行
          } else {
            alert('暂无权限查看')
          }
        } else {
          next() // 放行
        }
      })
      
      // 全局后置路由守卫 在成功切换路由后执行
      router.afterEach((to, from) => {
        if (to.meta.title) {
          document.title = to.meta.title
        }
      })
      
      export default router
      
  • 独享路由守卫

    • 有时项目中仅有几个需要路由守卫,那大可不必配置覆盖面较强的全局路由守卫,可配置独享路由守卫来完成需求,注意独享路由守卫没有后置路由守卫.

    • 示例 router/index.js

      const router = new Router({
        routes: [
          {
            path: '/about',
            component: About,
            meta: { title: '关于' },
            //独享路由守卫 没有afterEnter
            beforeEnter(to, from, next) {
              if (to.meta.isCheck) { // 判断要去的路由是否需要进行权限控制
                if (localStorage.getItem('school') === 'atguigu') { // 权限控制的具体规则
                  next() // 放行
                } else {
                  alert('暂无权限查看')
                }
              } else {
                next() // 放行
              }
            }
          }
      
  • 组件内守卫

    // 进入守卫,通过路由规则,进入组件时被调用
    beforeRouterEnter(to , from , next){
        
    },
    // 离开守卫,通过路由规则,离开该组件时被调用
    beforeRouterLeave(to , from , next){
        
    }
    
    </div>
    
    ```

10.路由组件所独有的两个生命周期钩子

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

11.路由守卫

  • 什么是路由守卫?

    对路由进行权限控制

  • 全局守卫

    • 示例 router/index.js

      import Router from 'vue-router'
      import About from '../pages/About.vue'
      import Home from '../pages/Home.vue'
      import Message from '../pages/Message.vue'
      import News from '../pages/News.vue'
      import Detail from '../pages/Detail.vue'
      
      
      // 创建并暴露一个路由器实例
      const router = new Router({
        routes: [
          {
            path: '/about',
            component: About,
            meta: { title: '关于' },
          },
          {
            path: '/home',
            component: Home,
            meta: { title: '主页' },
            children: [
              {
                path: 'message',
                component: Message,
                meta: { isCheck: true },
                children: [
                  {
                    name: 'detail',
                    path: 'detail/:id/:title',
                    component: Detail,
                    // 解构赋值写法
                    props({ params }) {
                      return {
                        id: params.id,
                        title: params.title
                      }
                    }
      
      
                  }
                ]
              },
              {
                path: 'news',
                component: News,
                meta: { isCheck: true },
              },
            ]
          }
        ]
      })
      
      // 全局前置路由守卫 在初次加载页面前 路由切换前执行
      router.beforeEach((to, from, next) => {
        /*
          1.  to对象存储需要切换到的组件的信息
              from对线存储切换前来自哪个组件的信息
              next方法是执行切换组件的方法
          2.to、from都有个meta属性,该属性为开发者存储组件的定制属性
        */
        if (to.meta.isCheck) { // 判断要去的路由是否需要进行权限控制
          if (localStorage.getItem('school') === 'atguigu') { // 权限控制的具体规则
            next() // 放行
          } else {
            alert('暂无权限查看')
          }
        } else {
          next() // 放行
        }
      })
      
      // 全局后置路由守卫 在成功切换路由后执行
      router.afterEach((to, from) => {
        if (to.meta.title) {
          document.title = to.meta.title
        }
      })
      
      export default router
      
  • 独享路由守卫

    • 有时项目中仅有几个需要路由守卫,那大可不必配置覆盖面较强的全局路由守卫,可配置独享路由守卫来完成需求,注意独享路由守卫没有后置路由守卫.

    • 示例 router/index.js

      const router = new Router({
        routes: [
          {
            path: '/about',
            component: About,
            meta: { title: '关于' },
            //独享路由守卫 没有afterEnter
            beforeEnter(to, from, next) {
              if (to.meta.isCheck) { // 判断要去的路由是否需要进行权限控制
                if (localStorage.getItem('school') === 'atguigu') { // 权限控制的具体规则
                  next() // 放行
                } else {
                  alert('暂无权限查看')
                }
              } else {
                next() // 放行
              }
            }
          }
      
  • 组件内守卫

    // 进入守卫,通过路由规则,进入组件时被调用
    beforeRouterEnter(to , from , next){
        
    },
    // 离开守卫,通过路由规则,离开该组件时被调用
    beforeRouterLeave(to , from , next){
        
    }
    
  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爪哇哇哇哇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值