一、vue介绍
https://www.bilibili.com/video/BV1zq4y1p7ga?p=75&vd_source=501c3f3a75e1512aa5b62c6a10d1550c
1、vue介绍
官网:https://cn.vuejs.org/
是一套用来构建用户界面的前端框架
构建用户界面
框架
是一套现成的解决方案,用来让程序员遵守欧框架规范编写页面功能
学习重点
Vue的用法、指令、组件(对UI结构的复用)、路由、vue
2、vue的特性 2.1 数据驱动视图
在使用了vue的页面里,vue会主动监听数据的变化,然后自动重新渲染页面结构
这样带来的优点:
注意:
2.2 双向数据绑定
双向数据绑定可以服务开发人员在不操作DOM的前提下,自动把想要的数据同步到数据源中
比如:填写表单时,自动会把页面上输入的内容同步到数据源中
双向数据绑定的好处:
如果没有vue,从页面获取数据,需要操作dom,如果需要把数据渲染到页面上,也要操作dom,如果这样的操作很多了,就会很不方便
有了vue,就可以实现不操作dom,就能做到数据的同步,无论是获取数据,还是渲染数据
当js数据有变化时,会自动渲染到页面上
页面上表单上拿到的数据有变化是,会被vue自动获取到,并更新到js中
3、了解vue底层原理 3.1 MVVM
1、MVVM是vue实现数据驱动视图和双向数据绑定的核心原理
2、MVVM是Model-View-ViewModel的缩写,即模型-视图-视图模型
3、MVVM三个特性
Model:表示当前页面渲染时所依赖的数据源,后端传递的数据
View:表示当前页面所渲染的DOM结构,代表UI组件,负责将数据模型转化成UI展现出来
ViewModel:表示vue实例,是一个同步View和Model的对象,MVVM模式的核心,是连接Model和View的桥梁
3.2 ViewModel
ViewModel是MVVM的核心,ViewModel把当前页面的数据源(Model)和页面结构(View)连接在一起
当数据源有变化时,会被ViewModel监听到,ViewModel会根据最新的数据源自动更新页面的结构
当表单元素的值有变化时,同时也会被ViewModel监听到,ViewModel会把变化后的最新的数据自动同步到Model数据源中
二、vue基础语法 1、第一行vue代码
需要三个步骤:
导入vue.js的脚本文件(表示是在导入vue库)
在页面中声明一个要被vue控制的DOM区域,比如一个div块
创建vm实例对象(vm是指ViewModel)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > {{username}} </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ el : "#app" , data : { "username" : "sam" } }) </script > </html >
上面代码中,new Vue是表示vue的构造函数,用来实例化一个vue对象(ViewModel对象)
new Vue构造函数里的值定义:
div里的传值写法
{{username}}表示将vue实例对象里的data里的username渲染到页面上
从下面就可以看到,data里的数据渲染到了页面上
1.1 使用工具调试vue
使用Vue Devtools 调试工具,就能看到vue的数据了
可以看到jam就是root根节点,也表示是id为app的div
上面代码里username是sam,可以在调试工具右边的data里进行数据修改,然后vue又自动渲染数据到页面上,达到了双向数据绑定
2、vue的指令
指令是vue提供的模板语法,用来帮助开发渲染页面的基本结构
指令分类:
内容渲染指令
属性绑定指令
事件绑定指令
双向绑定指令
条件渲染指令
列表渲染指令
3、内容渲染指令
该指令用来渲染DOM元素的文本内容
主要分为三个
3.1 v-text
将vm实例对象的data里的数据,渲染到页面上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <p v-text ="username" > </p > <p v-text ="gender" > 性别</p > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ el : "#app" , data : { "username" : "sam" , "gender" : "女" } }) </script > </html >
输出结果
v-text会覆盖元素自身原有的值,比如就是将div第二个p标签里的性别替换成了女,但这并不是我们想要的,我们希望在性别后面展示女
这个语法用的很少
3.2 插值表达式
{{}}语法主要用来解决v-text引发的覆盖默认文本内容的问题
{{}}语法叫做插值表达式(英文是:Mustache)
这个语法用的很多
问题解决:
3.2标题为什么要这么写的原因?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <p > 姓名:{{ username }}</p > <p > 性别: {{ gender }}</p > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ el : "#app" , data : { "username" : "sam" , "gender" : "女" } }) </script > </html >
3.3 v-html
要把包含html标签的字符串数据渲染为页面HTML元素,就需要v-html
使用v-text、差值表达式就会原样输出,只能渲染文本内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <p v-text ="username" > </p > <p > 姓名: {{ username }}</p > <p v-html ="username" > </p > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ el : "#app" , data : { "username" : "<h3 style='color:pink'>sam</h3>" , } }) </script > </html >
很清楚能看到,v-html把h3标签渲染到了页面上,因为H3标签里的文本内容的颜色变为设置的pink色
3.4 el属性注意事项
如果有多个相同HTML标签在页面结构中,用vue控制页面时,vue的el属性只会控制第一个同名的HTML标签,其余的都会原样输出,不会进行渲染,所以一般约定成俗的都是使用一个id名叫“app”一个大的div包裹我们需要编写的页面。
并且在以后的项目中,vue会自动配置控制区域的ID,不用我们手动写
4、属性绑定指令(重要)
如果需要给元素的属性动态绑定属性值,就需要用到v-bind属性绑定指令
什么时候使用使用属性绑定指令?
为元素的属性动态添加值时,就要考虑使用属性绑定指令了
任何元素需要使用动态值时,就可以使用属性绑定指令,这样属性就可以动态的接收值,达成了动态的展示不同的数据
比如要给input的placholder动态绑定值,就在placholder前面加一个v-bind或:,表示placholder属性的值时vue的数据源动态赋予的,达成了复用的效果
这个指令用的非常多
1 2 3 4 5 <input type ="text" v-bind:placeholder ="placeholderText" > <input type ="text" :placeholder ="placeholderText" >
完整代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > vue第一天</title > </head > <body > <div id ="app" > <input type ="text" v-bind:placeholder ="placeholderText" > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { "placeholderText" : "请输入用户名" , } }) </script > </html >
如果用插值表达式给元素属性绑定值,就会报错,插值表达式内容会原样输出并且控制台报错
5、JS语法在指令中的使用
在插值表达式和属性绑定指令中都可以添加js语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > vue第一天</title > </head > <body > <div id ="app" > <p > 1 + 2 = {{ 1 + 2 }}</p > <div :title ="'box' + index" > box盒子内容 </div > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { "placeholderText" : "请输入用户名" , "index" : 3 } }) </script > </html >
可以看到当鼠标放到box盒子内容上时,提示了title内容,内容就是拼接的box + index
6、事件绑定指令
vue中的v-on事件绑定指令,用来协助为DOM元素绑定事件监听,绑定的及时可执行函数
6.1 v-on语法 1 2 3 <button v-on:click ="addCount" > +1</button >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > vue第一天</title > </head > <body > <div id ="app" > <p > count的值:{{count}}</p > <button v-on:click ="addCount" > +1</button > <button v-on:click ="subCount" > -1</button > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { "count" : 0 , }, methods : { addCount : function ( ) { this .count += 1 console .log ("add count" ) }, subCount ( ) { this .count -= 1 console .log ("sub count" ) } } }) </script > </html >
6.2 methods中函数写法
在vue的实例对象中
methods里定义处理函数,推荐使用简写写法,就是函数名 小括号 花括号
6.3 this访问数据源数据
在vue的methods中定义了处理函数,如何来访问修改vue实例对象中的数据源的数据呢?
可以使用this关键字
类比到python中就是,在同一个实例对象中,可以使用self关键字+点号的方式一直访问这个对象的所有数据和方法,self就表示是这个实例对象本身
那么在同一个vue实例对象中,就可以用this.数据源中的数据来访问vue实例对象的数据源中的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <p > count的值:{{count}}</p > <button v-on:click ="addCount" > +1</button > <button v-on:click ="subCount" > -1</button > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { "count" : 0 , }, methods : { addCount : function ( ) { console .log ("add count" ) console .log (vm) }, subCount ( ) { console .log ("sub count" ) } } }) </script > </html >
在上面代码中,vue实例对象的methods里的addCount方法中打印了vm这个常量,下面是vm的值
vm值可以看出来
数据源中的count是vm这个实例对象的一个属性
数据源中的addCount、subCount是vm这个实例对象的方法
所以count、addCount、subCount对于vm实例对象来说,都可以用点的方式来调用
既然数据源中的count是vm这个实例对象的一个属性,那么在addCount方法里就可以通过vm.count访问到data里的count值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script> const vm = new Vue ({ "el" : "#app" , data : { count : 10 }, methods : { addCount ( ) { console .log ("vm.count的值" + vm.count ) } } }) </script>
不过vue中不推荐使用vm这个实例对象来访问属性或方法,而是使用this
从执行结果可以看到vm和this是全等的,那么就可以用this来代替vm访问vm这个实例对象里的属性和方法
注意:
用console.log(vm === this)时,一定不能在括号里再用+号拼接任何内容,否则返回结果是false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <script> const vm = new Vue ({ "el" : "#app" , data : { count : 10 }, methods : { addCount ( ) { console .log ("vm.count的值" + vm.count ) console .log (vm === this ) } } }) </script>
使用this访问属性,从执行结果来看,this.count和vm.count获取到的值一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <script> const vm = new Vue ({ "el" : "#app" , data : { count : 10 }, methods : { addCount ( ) { console .log ("vm.count的值" + vm.count ) console .log (vm === this ) console .log ("this.count:" + this .count ) } } }) </script>
6.4 事件绑定传参
在事件绑定时,可以对绑定函数进行传递参数,那么对应的vue实例对象里的methods中定义的事件绑定函数就要定义形参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <p > count的值:{{count}}</p > <button v-on:click ="addCount(2)" > +n</button > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { count : 10 }, methods : { addCount (n ) { this .count += n console .log ("this.count:" + this .count ) } } }) </script > </html >
从上面代码可以看出来,addCount函数接收一个形参n,那么在调用时就需要传递一个实参,比如2,那么每次点击+n这个事件,那么每次的count值都是递增+2
6.5 v-on简写格式
v-on使用的非常多,vue提供了简写方式@
注意:
原生DOM对象有onclick、oninput、onkeyup等原生事件,替换为vue的事件绑定后,对应的为
v-on:click
v-on:input
v-on:keyup
1 2 3 4 5 6 7 <body > <div id ="app" > <p > count的值:{{count}}</p > <button @click ="addCount(2)" > +n</button > </div > </body >
执行结果和事件绑定传参看到的结果一样,count的值都是递增+2
6.6 $event参数(不常用) 6.6.1 只有一个形参
当事件绑定函数里有一个形参时:
此时在div中调用该事件函数,但是不传递任何实参,此时事件函数里的形参值是有一个默认的值,比如时间绑定函数形参定义为e,那么e是的值是MouseEvent,也就是说e有一个自己的默认值
MouseEvent中有一个target属性,可以对元素进行样式的修改
此时在div中调用该事件函数,但是传递了一个实参,那么上面的默认值就没有了,传进来的实参是什么,那么e就是什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <p > count的值:{{count}}</p > <button @click ="addCount" > +1</button > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { count : 10 }, methods : { addCount (e ) { console .log (e) this .count += 1 if (this .count % 2 === 0 ) { e.target .style .backgroundColor = 'red' } else { e.target .style .backgroundColor = '' } } } }) </script > </html >
代码分析:
代码其实逻辑就是点击按钮时,当count值是偶数时,按钮颜色变为红色,为奇数不变色
能够看到e其实就是MouseEvent,每次点击时,e的值都同一个
6.6.2 有2个以上形参
vue提供了内置变量,固定写法:$event
既想传入一个实参,还想传MouseEvent参数,那么就可以就在调用事件函数时,将$event传进去
相应的vue实例对象的methods中,用形参e(约定成俗)来接收就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <p > count的值:{{count}}</p > <button @click ="addCount(1, $event)" > +1</button > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { count : 10 }, methods : { addCount (n, e ) { console .log (e) this .count += n if (this .count % 2 === 0 ) { e.target .style .backgroundColor = 'red' } else { e.target .style .backgroundColor = '' } } } }) </script > </html >
执行结果和只有一个形参一致
6.7 事件修饰符
vue提供了事件修饰符的功能,可以更方便的控制事件
事件修饰符
说明
.prevent
阻止默认行为,比如阻止a链接跳转,表单的提交等等
.stop
阻止事件冒泡(当页面有父子关系的标签时,只想打印子标签里面的内容,如果没有阻止,就会在打印子里面的内容时,也把父标签的内容也打印出来,为了不出现这种情况,就需要阻止事件冒泡)
6.8 按键修饰符
监听键盘事件时,需要判断详细的按键内容,此时就可以用按键修饰符
1 2 3 4 5 <input @keyup.enter ="submit" > <input @keyup.esc ="clearInput" >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > 提交数据:<input type ="text" @keyup.enter ="subData" > <br > 清空数据: <input type ="text" @keyup.esc ="clearInput" > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { count : 10 }, methods : { subData ( ) { console .log ("按了回车enter键" ) }, clearInput (e ) { console .log ("按了清除esc键" ) e.target .value = '' } } }) </script > </html >
7、双向绑定 7.1 v-model用法
vue提供了v-model双向数据绑定指令,用来在不操作DOM的前提下,快速获取表单数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <p > 用户名:{{ username }}</p > <input type ="text" v-model ="username" > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { username : "sam" }, methods : {} }) </script > </html >
上述代码分析:
p标签中的username值来自于data中的username,同时给p标签设置了一个v-model属性,并且指向了username
首先页面会将data里的username值渲染到页面上
当input输入框中的内容有改变时,vue会实时感知到,然后逆向再渲染到p标签中,这样data中的username值就变成了在input输入框中输入的内容,这样达到了不操作DOM就采集到表单标签的值的功能,这样vue实例里就可以通过this访问到实时更新的值
注意:
表单元素才可以使用v-model
表单元素有:
input输入框
textarea(大文本输入框)
select(下拉选择框标签)
7.2 v-model在select使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <select v-model ='city' > <option value ="" > 请选择城市</option > <option value ="1" > 北京</option > <option value ="2" > 上海</option > <option value ="3" > 河北</option > </select > <button @click ='subData' > 提交</button > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { username : "sam" , city : '' }, methods : { subData ( ) { console .log ('city:' + this .city ) } } }) </script > </html >
7.3 v-model修饰符
为了对用户输入内容更方便处理,v-model提供了三个修饰符
修饰符
作用说明
.number
自动将用户的输入值转为数值类型
.trim
自动过滤用户输入的首尾空白字符
.lazy
在“change”时而非“input”时更新
7.3.1 number修饰符
当从input获取到的值转为int时,就可以用number
下面的加法例子,如果没有.number修饰符,那么再输入其他数字,结果就会变为字符串拼接而不是加法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <input type ="text" v-model.number ='n1' > + <input type ="text" v-model.number ='n1' > = <span > {{ n1 + n2 }}</span > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { n1 : 2 , n2 : 2 }, methods : { } }) </script > </html >
7.3.2 trim修饰符
将表单获取的数据,去除首尾空白字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <input type ="text" v-model.trim ='username' > <button @click ='getUserName' > 获取用户名</button > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { username : "" }, methods : { getUserName ( ) { console .log (this .username ) } } }) </script > </html >
7.3.3 lazy修饰符
当输入内容只想在失去焦点时才同步给vue,那么就可以使用.lazy修饰符,因为vue双向绑定实时的,每次更新肯定会有性能损耗,当不需要这样,就可以使用lazy
1 2 3 4 5 6 <body > <div id ="app" > <input type ="text" v-model.lazy ='username' > <button @click ='getUserName' > 获取用户名</button > </div > </body >
8、条件渲染指令
条件渲染指令可以根据条件来控制DOM的显示与隐藏
有两个指令:
8.1 v-if
v-if原理:每次动态创建或移除元素,实现元素的显示和隐藏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <p v-if ='flag' > 这是被v-if控制</p > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { flag : true }, methods : {} }) </script > </html >
从上面控制台的vue列和Elements列可以看出,当flag为true时,v-if的p标签就显示出来了
那把flag改为false时,可以看到整个v-if的p标签直接被移除了
8.2 v-show
v-show原理:动态为元素添加或移除display: none样式,实现元素的显示和隐藏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <p v-show ='flag' > 这是被v-show控制</p > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { flag : true }, methods : {} }) </script > </html >
从上面控制台的vue列和Elements列可以看出,当flag为true时,v-show的p标签就显示出来了
那把flag改为false时,可以看到整个v-show的p标签没有被移除,而是加了display: none的属性达到隐藏的效果
8.3 v-else-if
相当于是用v-if的一个分支,但是必须要和v-if配套使用,否则不会被识别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <body > <div id ="app" > <div v-if ="count === 1" > 1级</div > <div v-else-if ="count === 2" > 2级</div > <div v-else ="count === 3" > 3级</div > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { flag : true , count : 1 } }) </script >
9、列表渲染指令 9.1 v-for
主要是用来将数组循环渲染成一个列表的结构
v-for用item in items形式的语法
items是待循环的数组
item是被循环的每一项
需要循环哪个DOM结构,就给那个页面结构加v-for,所以v-for在标签元素的后面紧跟,就代表需要循环标签元素,所以一定注意需要跟在<标签元素的后面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 举例 <li v-for ="item in userinfos" > name: {{ item.name }}</li > # vue代码 <script > const vm = new Vue ({ "el" : "#app" , data : { userinfo : [{ id : 1 , name : "Sam" }, { id : 2 , name : "Jam" }, { id : 3 , name : "Tom" }, ] } })
9.2 v-for支持索引
v-for支持可选的第二个参数,也就是当前项的索引
(item, index) in items
注意:
v-for中的item项和index索引都是形参,所以可以替换成别的形参名
索引是按需添加,不强制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 举例 <li v-for ="(item, index) in userinfos" > index:{{ index }}, name: {{ item.name }}</li > # vue代码 <script > const vm = new Vue ({ "el" : "#app" , data : { userinfo : [{ id : 1 , name : "Sam" }, { id : 2 , name : "Jam" }, { id : 3 , name : "Tom" }, ] } })
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <style > li { list-style : none; } table { border-collapse : collapse; width : 200px ; height : 160px ; } table , th , td { border : 1px solid black; text-align : center; } </style > </head > <body > <div id ="app" > <table > <thead > <th > index</th > <th > id</th > <th > name</th > </thead > <tr v-for ="(item, index) in userinfo" > <td > 索引:{{ index }}</td > <td > {{ item.id }}</td > <td > {{ item.name }}</td > </tr > </table > </div > </body > <script src ="../lib/vue/vue_v2.6.14.js" > </script > <script > const vm = new Vue ({ "el" : "#app" , data : { userinfo : [{ id : 1 , name : "Sam" }, { id : 2 , name : "Jam" }, { id : 3 , name : "Tom" }, ] } }) </script > </html >
9.3 v-for推荐添加key
vue官方推荐,只要用到了v-for指令,那么一定要绑定一个:key属性,
key值注意事项:
对于key的值只能是字符串或数字类型
并且key的值不能重复,否则会报Duplicate keys detected的错误
并且尽量以当前循环项的id作为key的值
不推荐index作为key的值,会出现数据错乱
原因是如果添加数据时,添加数据成功以后,当前数据的索引值会变化,所以索引就不唯一了,并且和数据不是唯一绑定的,只有id才是和数据唯一绑定的
指定key可以提升性能,放置列表错乱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <body > <div id ="app" > <table > <thead > <th > index</th > <th > id</th > <th > name</th > </thead > <tr v-for ="(item, index) in userinfo" :key ="item.id" > <td > 索引:{{ index }}</td > <td > {{ item.id }}</td > <td > {{ item.name }}</td > </tr > </table > </div >
三、npm使用 1、npm使用虚拟环境
npm可以像python一样,使用虚拟环境来管理多个版本的node,可以在电脑中安装多个版本的node以及npm,方便我们使用
nvm就是一款可以支持多个版本node和npm的工具,官网:https://github.com/nvm-sh/nvm
下图是nvm安装的情况,可以看到有个”- >”表示当前使用的npm版本,当然nvm可以使用其他版本,具体使用查看nvm的官方地址即可
2、nrm管理镜像仓库
nrm是用来对npm的镜像仓库进行管理的工具,官网:https://github.com/Pana/nrm
对于nrm查看镜像列表没有显示星号的解决办法
1 2 3 4 5 6 # 打开安装nrm目录下找到cli.js ,一般是在虚拟环境的目录:~/.nvm/ versions/node/v16.20 .0 /lib/node_module/nrm # 找到如下代码,将&&换成||,然后保存退出 if (hasOwnProperty (customRegistries, name) && (name in registries || customRegistries[name].registry === registry.registry )) { registry[FIELD_IS_CURRENT ] = true ; customRegistries[name] = registry; }
星号表示当前的npm镜像仓库是什么,我们可以对其进行增加,后续切换镜像仓库会非常方便
3、查看npm的仓库镜像源
四、vue-cli介绍 1、vue-cli介绍
vue-cli是vue.js开发的标准工具,简化基于webpack创建工程化vue项目的过程
网址:https://cli.vuejs.org/zh
以下来源于vue-cli官网
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:
通过 @vue/cli 实现的交互式的项目脚手架。
通过 @vue/cli + @vue/cli-service-global 实现的零配置原型开发。
一个运行时依赖 (@vue/cli-service),该依赖:
可升级;
基于 webpack 构建,并带有合理的默认配置;
可以通过项目内的配置文件进行配置;
可以通过插件进行扩展。
一个丰富的官方插件集合,集成了前端生态中最好的工具。
一套完全图形化的创建和管理 Vue.js 项目的用户界面。
Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject。
1.1 vue-cli组件
Vue CLI 有几个独立的部分——如果你看到了我们的源代码 ,你会发现这个仓库里同时管理了多个单独发布的包。
1.1.1 CLI
CLI (@vue/cli) 是一个全局安装的 npm 包,提供了终端里的 vue 命令。它可以通过 vue create 快速搭建一个新项目,或者直接通过 vue serve 构建新想法的原型。你也可以通过 vue ui 通过一套图形化界面管理你的所有项目。我们会在接下来的指南中逐章节深入介绍。
1.1.2 CLI服务
CLI 服务 (@vue/cli-service) 是一个开发环境依赖。它是一个 npm 包,局部安装在每个 @vue/cli 创建的项目中。
CLI 服务是构建于 webpack 和 webpack-dev-server 之上的。它包含了:
加载其它 CLI 插件的核心服务;
一个针对绝大部分应用优化过的内部的 webpack 配置;
项目内部的 vue-cli-service 命令,提供 serve、build 和 inspect 命令
2、vue-cli安装和卸载 2.1 vue-cli安装
安装vue-cli需要对node有版本要求
Vue CLI 4.x
如果已经安装过了,会提示下方报错
安装之后,就可以在命令行中访问 vue 命令
还可以用这个命令来检查其版本是否正确
2.2 vue-cli卸载 1 2 3 4 5 npm uninstall vue-cli -g npm uninstall @vue/cli -g
3、vue初体验 3.1 创建第一个vue项目
使用vue-cli创建工程化的vue项目,vue2项目和vue3项目创建的步骤一模一样,就是vue版本选择时不同,所以下面的步骤是以vue2为例子来创建vue项目,vue3也同样适用
1 2 vue create tester-tools
可以看到有两个提示
第一个提示是询问我们需要更换npm的镜像源吗?可以选否
第二个提示是询问pick a preset,表示请选择预设,可以用上下箭头选择
如果选择Default ([Vue 3] babel, eslint) ,会自动安装vue3,并安装babel、eslint
如果选择Default ([Vue 2] babel, eslint) ,会自动安装vue2,并安装babel、eslint
建议选择Manually select features,表示手动选择需要的功能,这样定制更高
可以看到有很多选项,有选中的表示已经选择了该功能
Babel解决js兼容性,必须选中
TypeScript是微软的一种js语言,可以不选
Progressive Web App (PWA) Support是渐进式的框架,可以不选
Router是路由,可以不选
Vuex,可以不选
CSS Pre-processors是css预处理器,建议选中,
Linter / Formatter是代码风格,可以不选
如果团队中有人用双引号,有人用单引号,那么这个工具就会报错,项目跑步起来,所以这个插件建议不安装
Unit Testing是单元测试,可以不选
E2E Testing是端对端测试,可以不选
最终选择完的结果如下
选择好以后,按回车进行下一步,会提示选择vue的版本
选择CSS预处理,这里选择less后回车
接下来继续提示下面的插件的配置文件想放到package.json,还是放到插件的独立的配置文件
这里建议选择插件的独立的配置文件,这样可以更加独立的维护
因为package.json是项目依赖的管理文件,肯定不希望这些插件的配置信息在里面
此时会提示:是否想当前预设保存给未来的项目,这里可以选择否,输入N,后面创建vue项目可以自定义选择别的插件
也可以选择y,当前配置就会给后面创建项目的时候来使用
接着会提示,选择安装依赖的包管理器,可选Yarn或NPM,这里可以选择NPM
选择NPM后,就会开始创建项目了
到这第一个vue项目就创建好了
3.2 运行第一个vue项目
从上面的创建好vue项目后的提示可以看到提示了我们如何运行项目
1 2 3 4 5 $ cd tester-tools $ npm run serve
从上面的启动项目可以看出来
那访问下本机的IP和端口,看下我们创建的第一个vue项目,浏览器打开http://localhost:8080/
可以看到浏览器打开是VUE的默认欢迎页面,那么我们的第一个vue项目就启动成功了
注意:
npm run serve这个窗口不要关闭,关闭了上面访问http://localhost:8080/就无法访问了
3.3 vue项目结构
上面我们成功的创建并启动了第一个vue项目,下面来分析下vue项目的目录结构,更加充分了解vue项目
从下面截图可以看出,vue项目基本分为下面几个目录
3.3.1 src目录
src目录,见名知意也知道是source源码的意思,表示所有写的代码都在这个目录下
src目录的目录结构
assets目录
components(重要)
将封装好、可复用的组件放到components目录中
App.vue
main.js
是项目入口文件,整个项目运行时,优先运行main.js
3.3.2 public目录
public目录,公共目录,里面存储了存放公共内容的目录,常见的内容有
favicon.ico,网站的icon
index.html,单页面项目,所有内容都是在index.html里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <template > <div id ="app" > <img alt ="Vue logo" src ="./assets/logo.png" > <HelloWorld msg ="欢迎来到VUE项目" /> </div > </template > <script > import HelloWorld from './components/HelloWorld.vue' export default { name : 'App' , components : { HelloWorld } } </script > <style lang ="less" > #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 >
可以看到里面有div,id是app,就和实例化vue里传递给el属性的值是同一个,vue控制的就是这个div块
3.3.3 node_modules目录
下载的第三方包的存储目录,所以这个目录尽量不需要传到git管理仓库,否则项目目录的磁盘占用会很大
3.4 vue运行流程
工程化的vue项目,vue的功能
核心概念:通过src/main.js将src/components/App.vue的页面UI结构渲染到index.html的指定区域中
App.vue用来编写需要被渲染的模板结构
index.html中预留一个el区域
main.js把App.vue的页面UI结构渲染到index.html预留的el区域
下面是vue2中的main.js的文件内容
1 2 3 4 5 6 7 8 9 10 11 12 13 import Vue from 'vue' import App from './App.vue' Vue .config .productionTip = false new Vue ({ render : h => h (App ), }).$mount('#app' )
下面是vue3中的main.js的文件内容
1 2 3 4 5 import { createApp } from 'vue' import App from './App.vue' createApp (App ).mount ('#app' )
下面是src/components/App.vue文件的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <!-- <HelloWorld msg="Welcome to Your Vue.js App"/> --> <HelloWorld msg="欢迎来到VUE项目"/> </div> </template> <script> import HelloWorld from './components/HelloWorld.vue' export default { name: 'App', components: { HelloWorld } } </script> <style lang="less"> #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>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 下面代码是public/index.html <!DOCTYPE html > <html lang ="" > <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" > <link rel ="icon" href ="<%= BASE_URL %>favicon.ico" > <title > <%= htmlWebpackPlugin.options.title %></title > </head > <body > <noscript > <strong > We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript > <div id ="app" > </div > </body > </html >
注意:
main.js中的render函数把App.vue的页面结构渲染给index.html时,render函数本质会将App.vue里UI结构,完全替换index.html里的<div id="app"></div>这一块,相当于是全部替换了,此时去查看页面结构,会发现没有id为app的div块,显示的页面结构是App.vue里的页面结构内容
3.4.1 main.js中的vue实例对象
五、组件(component) 1、vue组件化开发 1.1 vue组件化开发
根据封装的意思,将页面上可重用的UI结构封装为组件,进而方便项目的开发和维护
vue本身是支持组件化开发的框架
vue中规定
组件的文件后缀名是.vue
第一个vue项目的src/App.vue这个文件就是一个vue组件
1.2 vue组件开发三部曲
每个xxx.vue组件都由3部分组成,分别是:
template:组件的模板结构
script:组件的JavaScript行为
style:组件的样式
1.2.1 组件中的template节点
vue规定每个组件对应的模板结构,需要定义到<template>节点
注意:
<template>节点是vue提供的容器标签,只能有包裹性质的作用,它不会被渲染为真正的DOM元素
组件的<template>节点中支持所有的指令语法,比如:
插值表达式
v-bind
v-on
v-for等等
1 2 3 4 5 6 7 8 <template> <div > <p > 这是index.vue组件</p > <p > 这是我的第一个组件</p > <p > username: {{ username }}</p > </div > </template>
1.2.2 组件中的script节点
vue规定<script>节点是可选的,可以在<script>节点中封装组件的javascript业务逻辑
1 2 3 4 5 <script> export default { } </script>
组件的script节点下name节点
可以通过name节点为当前组件定义一个名称,在使用vue-devtools进行调试时,自定义组件名称可以很清晰区分出来
1 2 3 4 5 6 7 <script> export default { name : "MyApp" , } </script>
组件的script节点下data节点表示需要被渲染的数据源
注意事项:
xx.vue组件中的data不能像之前vue实例对象里的一样,不能指向对象
组件中的data必须是一个函数,然后data这个函数将数据源给返回
如果data数据源指向了对象,那么就会报错,报错如下
1 2 3 4 5 6 7 8 9 10 <script> export default { data : { username : "sam" } } </script>
需要让data是一个函数,这样组件中的template区域才可拿到正确的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 <script> export default { data ( ){ return { username : "sam" } } } </script>
组件的script节点下methods节点
组件中定义methods方法和vue实例对象中一样,在methods中定义方法即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <template> <div> <p>这是index.vue组件</p> <p>这是我的第一个组件</p> <br> <p>用户名{{ username }}</p> <button @click="showMsg">点击修改用户名</button> </div> </template> <script> // 默认导出,固定写法 export default { // data数据源 // data数据源必须是一个函数 data(){ return { username: "修改前是:admin" } }, // methods中定义方法 methods: { showMsg() { this.username = "修改后是:sam" } } } </script> <style> p { font-size: 24px; color: deeppink; } </style>
组件中methods里的方法的this是什么?
在vue组件中,this表示当前组件的实例对象
组件的实例对象中也有username属性,那么就可以直接用this来调用
1.2.3 组件的style节点
vue规定组件内<style>节点是可选的,在<style>节点中编写美化当前组件的UI结构
<style>标签上的lang=”css”属性是可选的,表示为所使用的样式语言,默认支持普通css语法,可选less、scss等
1 2 3 4 5 6 <style> h1 { font-size : 14px; } </style>
1.3 vue组件注意事项 1.3.1 vue组件的唯一根节点
在vue2.x中,<template>只能有一个根节点,否则会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 下面在组件的template中定义了两个div,就会报错 <template> <div> <p>这是index.vue组件</p> <p>这是我的第一个组件</p> <br> <p>用户名{{ username }}</p> <button @click="showMsg">点击修改用户名</button> </div> <!-- 这里是第二个div,但是会报错 --> <div> <p>第二个div</p> </div> </template>
在vue3.x中,<template>支持定义多个根节点
1.3.2 启动less语法
在创建vue工程化项目时,选择了less插件,那如何在vue组件中启用less呢?如下代码
1 2 3 4 5 6 <style lang="less"> p { font-size : 24px ; color : deeppink; } </style>
2、组件的使用 2.1 组件关系
组件创建好以后,彼此之间是相互独立的,不存在父子关系
组件嵌套后,才产生了父子关系、兄弟关系
组件A嵌套了组件B和组件C,组件A和(组件B、组件C)是父子关系
组件B和组件C是兄弟关系
vue中注册组件分为下面两种方式
全局注册:被全局注册的组件,可在全局任何一个组件内使用
局部注册:被局部注册的组件,只能在当前注册的范围内使用
2.2 使用组件三步骤
当组件写好以后,如何使用组件有三个步骤
步骤一:在组件(xxx.vue的script节点中)中使用import语法导入需要的组件
步骤二:在组件(xxx.vue的script节点中)中使用components节点注册组件
步骤三:在组件(xxx.vue的template节点中)中以标签形式使用注册的组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template> <div> <!-- 步骤3:以标签形式使用注册的组件 --> <Card></Card> </div> </template> <script> // 步骤1:导入components目录下的自定义组件 import Card from '@/components/Card.vue' export default { // 步骤2:在components节点下注册组件 components: { Card } } </script> <style lang="less"> </style>
需要注意事项是:
步骤1中导入组件的@符号,是表示从项目的./src目录开始查找组件,这个是webpack里的内容,vue使用@很有好的自定义了这个快捷方式,表示从./src目录下开始导入components目录里的组件
步骤2中在components节点注册组件时,只写了Card,表示{“Card”:“Card”},就是说这个对象的key和value都是一样的,那就可以简写为Card
推荐vscode插件:
2.2.1 全局注册组件
在vue项目的main.js入口文件中,通过Vue.component()方法来注册全局组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import Vue from 'vue' import App from './App.vue' import Count from '@/components/Count.vue' Vue .component ("Count" , Count )Vue .config .productionTip = false new Vue ({ render : h => h (App ), }).$mount('#app' )
全局注册组件
在根组件App调用组件Left、Right组件
上面代码中:
Count组件被注册为全局组件
根组件App引入了组件Left、组件Right
组件Left、组件Right中又分别引入了全局组件Count
最终页面展示的效果
2.2.3 局部注册组件
在单独的组件A中的components节点下注册了组件B,那么组件B只能在当前组件A中使用,不能被其他组件C使用,组件B就是私有组件
2.2.4 组件注册名称写法
在进行组件注册时,定义组件注册名称的方式有:
使用keybab-case命名法,俗称短横线命名法,比如:my-swiper、my-date等
使用PascalCase命名法,俗称帕斯卡命名法或大驼峰命名法,比如MySwiper等
1 2 3 4 5 6 7 8 import Swiper from './components/Swiper.vue' import Test from './components/test.vue' vue.component ("my-test" , Swiper ) vue.component ("MySwiper" , Swiper )
通过name属性注册组件
在注册组件期间,除了可以直接提供组件的注册名称外,还可以把组件的name属性作为注册后组件的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template> </template> <script > export default { name : 'Swiper' , } </script > <style > </style >
1 2 3 4 import Swiper from './components/Swiper.vue' vue.component (Swiper .name , Swiper )
2.2.5 组件样式冲突
在.vue组件中的样式会全局生效,因此会造成多个组件之间的样式冲突问题,导致的根本原因:
所有组件的DOM结构,都是基于唯一的index.html页面进行渲染
每个组件的样式,都会影响整个index.html页面的DOM结构
为每个组件分配唯一的自定义属性,在编写组件样式,使用属性选择器来控制样式的作用域,但是每个组件都要写唯一自定义属性,比较麻烦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <div class ="box" data-v-001 > <h3 data-v-001 > 首页</h3 > </div > </template> <script > export default { name : 'Swiper' , } </script > <style > // 用过中括号的属性选择器,来防止组件之间的样式冲突问题 // 给每个组件分配的自定义属性都是唯一的 .box [data-v-001] { color : red; } </style >
vue为了解决上述出现的问题,vue为style节点提供了scoped属性,从而防止组件的样式冲突问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template> <img alt ="Vue logo" src ="./assets/logo.png" > <p > 这是App.vue</p > <UserList /> </template > <script lang ="ts" > import { defineComponent } from 'vue' ;import UserList from './components/UserList.vue' export default defineComponent ({ name : 'App' , components : { UserList , } }); </script > <style lang ="less" scoped > #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 ; } p { color : red; } </style >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <template> <div > <p > 姓名:{{uname}},年龄:{{age}} <a href ="" > 点击抽奖</a > </p > </div > </template> <script > export default { name : "UserList" , data ( ) { return { uname : "sam" , age : 19 } } } </script > <style scoped > p { color : brown; } p a { color : brown; } </style >
从渲染的html结构来看,vue的style节点设置了一个scoped属性以后,vue会自动给组件设置了一个唯一的自定义属性,用来防止组件之间的样式冲突问题
可以看到UserList.vue组件有了一个data-v-4a3fa6b9的属性,不需要我们手动再去写自定义的唯一属性了
并且后面的的data-v-7ba5bd90这个属性是父组件里的唯一自定义属性
2.2.6 /deep/样式穿透
如果给当前组件的style节点添加了scoped属性,则当前组件的样式对其子组件是不生效的,如果想让某些样式对子组件生效,可以使用/deep/深度选择器
1 2 3 4 5 6 7 8 9 10 11 <style> h3 { color : pink; } /deep/ h3{ color : pink; } </style>
2.3 组件props属性
props是组件的自定义属性,组件的调用者可以通过props属性将数据传递到子组件内部,供子组件内部进行使用
props作用:父组件通过props向子组件传递要展示的数据
props好处:提高组件的复用性
2.3.1 props属性基本使用
在封装vue组件是,可以把动态的数据项声明为props自定义属性,自定义属性可以在当前组件的模板结构中直接被使用
1 2 3 4 5 6 export default { props : ['自定义属性A' , '自定义属性B' , '其它自定义属性...' ] }
在封装通用组件的时候,合理的使用props属性可以极大提高组件复用性
上面这句话怎么理解更好呢?
比如封装了一个组件A,组件B和组件C地方都用到了这个组件A,但是希望组件B和组件C给组件A传进去的值是不一样,使用props属性,就可以在组件B和组件C调用组件A,传进去不同的值,后面的动态路由就可以使用props进行传参
props作为自定义属性,允许调用者通过自定义属性,给当前组件指定初始值
这句话理解为调用封装好的组件时,调用格式为<组件名></组件名>,封装好的组件有props属性时,就可以再调用的时候,将props里定义的属性拿过来到调用格式里当做属性,格式会变为<组件名 props中的属性></组件名>,表示设置组件的props属性的默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // Count.vue组件 <template> <div class="count"> <p>组件Count</p> <p>count:{{ init }}</p> <button @click="addCount">+1</button> </div> </template> <script> export default { // 自定义props属性的初始化值 props: ["init"] } </script>
从上面的Count组件中可以看到props自定义属性,那么调用Count组件方就可以使用这些自定义属性了
比如Left.vue中就以标签形式调用Count组件,并且给Count组件标签中添加Count组件的props自定义属性init
1 2 3 4 5 6 7 8 9 10 // Left.vue组件 <template> <div class="left"> <p>这是Left组件</p> <hr> // 这里首先Count被注册为全局组件了 // 然后Count组件有一个自定义的init属性 <Count init="9"></Count> </div> </template>
此时打开浏览器页面查看Left.vue结构,从下面可以看到Left组件中的Count组件的props中的init自定义属性值就是传进去的9,但是要注意的是,此时的init值的类型是字符串,那么在页面操作Left组件的+号操作,可以看到Count组件中的加法结果其实是字符串拼接了,因为init值是字符串9,每次+1都和9这个字符串拼接
注意:
父组件传递给了子组件中未声明的props属性,则传递进来的这些props属性会被忽略,无法被子组件使用
比如子组件的props只有title属性,但是父组件调用子组件时,还给子组件传了title和author属性,那么子组件只能使用title属性,不能使用author属性
2.3.2 动态绑定props属性
外界组件调用方想动态的给封装好的组件的props自定义属性传值时,就可以对props的值使用属性绑定指令(v-bind)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // Left.vue组件 <template> <div class="left"> <p>这是Left组件</p> <hr> // 这里首先Count被注册为全局组件了 // 然后使用属性绑定指定,动态的进行props属性绑定,提高了组件的复用性 <Count :title="info.title" :author="'post by' + info.author"></Count> </div> </template> <script> export default { data(){ return: { info: { "title": "vue的用法", "author": "sam" } } } } </script>
2.3.3 props的大小写命名
组件中如果使用”camelCase(驼峰命名法)”声明了props属性的名称,则有两种方式可以绑定属性的值
1 2 3 4 5 6 7 8 9 10 11 <template> <p > 发布时间:{{ pubTime }}</p > </template> <script > export default { name : "Count" , props : ['pubTime' ] } </script >
1 2 3 4 5 6 7 <template> <!- - 方式1 :使用"驼峰命名" 的形式为组件绑定属性的值> <my-post pubTime ="" 1999 "> </my-post > <!- - 方式2 : 使用"短横线分隔命名" 的形式为组件绑定属性的值> <my-post pub-time ="" 1999 "> </my-post > </template>
2.4 组件props验证 2.4.1 对象类型的props节点
在2.3小节的组件props属性中,我们在子组件中的props属性使用的是列表形式,无法对props属性进行数据类型的校验
vue组件除了使用列表形式的props属性,还可以使用对象类型的props属性节点,来对每个props属性的数据类型进行校验
1 2 3 4 5 6 7 8 9 10 11 <script> export default { name : "Count" , props : { title : String , state : Boolean } } </script>
如果传递给子组件的props属性的数据类型和子组件中定义的props属性的数据类型不一致,就会在浏览器的console调试面板中有提示告警信息
可以看到提示了Invalid prop,无效的prop,表示期望是Boolean,但实际得到了String
2.4.2 基础类型的检查
可以为组件的prop属性指定基础的校验类型,从而防止组件的使用者为其绑定一个错误的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script> export default { props : { title1 : String , title2 : Number , title3 : Boolean , title4 : Array , title5 : Object , title6 : Date , title7 : Function , title8 : Symbol , } } </script>
2.4.3 多个可能的类型
可以对某一个prop属性指定多个数据类型
1 2 3 4 5 6 7 8 <script> export default { props : { title1 : [String ,Boolean ,Number ] } } </script>
2.4.4 必填项校验
如果组件的某个prop属性是必填项,必须要让组件调用者为其传递属性的值,需要通过配置对象的形式,为prop属性定义验证规则
注意:
当prop属性的required为true时,表示当前属性的值时必填项,如果调用者没有指定该属性的值,就会在console中报错
1 2 3 4 5 6 7 8 9 10 11 12 <script> export default { props : { title : { type : String , required : true }, state : Boolean } } </script>
2.4.5 属性默认值
封装组件时,可以为某个prop属性指定默认值
1 2 3 4 5 6 7 8 9 10 11 12 <script> export default { props : { title : { type : String , default : "vue是最棒的!" }, state : Boolean } } </script>
2.4.6 自定义验证函数
在封装组件时,可以为prop属性定义自定义的验证函数,从而对prop属性进行精准控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script> export default { props : { title : { type : String , default : "vue是最棒的!" , validator (value ) { return ["vue1" , "vue2" ].indexOf (value) !== -1 } } } } </script>
如果组件调用方出传的prop的属性值不是validator函数中return列表的任意一个,则报错
2.5 组件的计算属性
计算属性本质上是一个函数,可以实时监听data中数据的变化,并return一个计算后的新值,提供给组件渲染
计算属性需要以函数的形式声明到组件的computed选项中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <p > {{ count }} + 2 = {{ add }} </p > </template> <script > export default { data ( ) { return { count : 1 } }, computed : { add ( ) { return this .count + 2 } } } </script > <style scoped > </style >
注意:
计算属性侧重于得到一个计算的结果,所以计算属性中必须使用return返回计算的新值
计算属性必须定义在computed节点中
计算属性必须是一个function函数
计算属性必须有返回值
计算属性必须当做普通属性返回
2.6 组件的watch侦听器
watch侦听器是允许开发者监视数据的变化,从而对数据的变化做特定的操作,比如监听用户名的变化而发起请求,判断用户名是否可用
2.6.1 watch侦听器的基本用法
需要在watch节点下,定义自己的侦听器,也就是声明属于自己的侦听函数方法
我们需要监听哪个值的变化,那就把需要监听的值的名字拿过来当成函数名,写在watch节点下,然后watch侦听器的侦听函数的形参列表,第一个值是”变化后的新值”,第二值是”变化之前的旧值”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template> <p > {{ count }} + 2 = {{ add }} </p > <p > 您输入了:{{ msg }}</p > 姓名:<input type="text" v-model.trim ="msg" > </template> <script > export default { data ( ) { return { count : 1 , msg : "" } }, computed : { add ( ) { return this .count + 2 } }, watch : { msg (newVal, oldVal ){ console .log ("newVal:" + newVal); console .log ("oldVal:" + oldVal); }, }, } </script > <style scoped > </style >
每次input框输入完成以后,就会打印出新值和旧值,特别适合用来做用户名、商品名等唯一性的校验,比如给用户名、商品名进行侦听,每次变化时就发起ajax请求,由接口返回用户名、商品名是否可用
默认情况下,组件在初次加载完成后不会调用watch侦听器,如果想让watch侦听器在组件初始化时就被立即调用,那么可以使用immediate选项
比如组件的data中某个变量被watch侦听了,并且这个变量有初始值,如果组件首次加载,但是侦听器中的侦听函数是不会被调用的,所以就需要immediate选项来让组件侦听器在组件初始化时就可以使用
注意:
需要被监听的值不是函数了,而是一个对象,并且对象中有一个固定的handler函数,表示来接受值的前后变化
在被监听值的对象中定义immediate: true,表示组件加载完成后立即调用一次当前msg的watch侦听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <template> <p > 您输入了:{{ msg }}</p > 姓名:<input type="text" v-model.trim ="msg" > </template> <script > export default { data ( ) { return { msg : "admin" } }, watch : { msg : { handler (newVal, oldVal ){ console .log ("newVal:" + newVal); console .log ("oldVal:" + oldVal); }, immediate : true }, }, } </script > <style scoped > </style >
从上面代码可以看出:
data的msg初始值是admin,并且对msg值设置了侦听器以及immediate为true
那么刷新页面后,组件加载完毕,会自动调用一次msg值的侦听器,我们在console中就看到newVal:admin和”oldVal:undefined”
为什么”oldVal”的值是undefined?
因为是组件加载完毕后就调用msg的侦听器函数,那么msg的初始值admin就是组件获取到的最新值,那么msg的旧值没有自定义,所以就是undefined
2.6.3 deep选项
当watch侦听的是一个对象时,如果对象中的某些值发生了变化,则无法被侦听到,此时需要使用deep属性
注意:
当侦听器的hanler函数的旧值用不到,就可以在handler函数中不传第二个oldVal
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <template> <p > 姓名:{{ user.name }}</p > 姓名:<input type="text" v-model.trim ="user.name" > </template> <script > export default { data ( ) { return { user : { name : "sam" } } }, watch : { user : { handler (newVal ){ console .log ("newVal:" + JSON .stringify (newVal)); if (newVal.name === "vue3" ) { console .log ("用户名可用:" + JSON .stringify (newVal)); } }, immediate : true , deep : true }, }, } </script > <style scoped > </style >
从上面代码看出:
当对user对象的侦听器设置了deep为true以后,当user对象的name发生了变化时,那就会自动调用handler函数,并且当name等于”vue3”时,还执行了user侦听器函数中if语句的console函数
2.6.4 监听对象单个属性的变化
监听对象时,如果我们设置了true,那么任何对象的任意一个值发生了变化,那么都会调用一次侦听器,我们只想让单个属性发生变化才进行侦听,就需要对单个属性设置侦听器了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <script> export default { data ( ) { return { user : { name : "sam" , age : 0 } } }, methods : { addAge ( ){ console .log ("年龄+1" ) this .user .age += 1 } }, watch : { "user.name" : { handler (newVal ){ console .log ("newVal:" + newVal); if (newVal === "vue3" ) { console .log ("用户名可用:" + newVal); } }, immediate : true , }, }, } </script>
2.6.5 计算属性和侦听器的区别
计算属性和侦听器侧重的应用场景不同
计算属性侧重于监听多个值的变化,得到return一个新值
侦听器侧重于监听单个数据的变化,最终执行我们设置的逻辑,并且不需要任何返回值
2.7 组件的生命周期
组件的运行过程如下图所示,其中组件运行最关键的就是”以标签形式使用组件”
组件的生命周期是指:组件从【创建】-【运行(渲染)】-【销毁】的整个过程,主要是组件的运行时间段
2.7.1 生命周期函数
vue为组件内置了不同时刻的生命周期函数,生命周期函数会随着组件的运行而自动调用
当组件在内存中被创建以后,会自动调用created函数
当组件被成功渲染到页面上以后,会自动调用mounted函数
当组件被销毁完成以后,会自动调用unmounted函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <template> <LifeCycle v-if ="flag" > </LifeCycle > <button @click ="hiddenLifeCycle" > 点我隐藏LifeCycle组件</button > <button @click ="showLifeCycle" > 点我显示LifeCycle组件</button > </template> <script > import LifeCycle from "@/components/LifeCycle" ;export default { name : "Left" , components : { LifeCycle }, data ( ) { return { flag : true , } }, methods : { hiddenLifeCycle ( ){ this .flag = false }, showLifeCycle ( ){ this .flag = true }, } } </script > <style scoped > button { margin-right : 15px ; } </style >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <p > 这是LifeCycle组件</p > </template> <script > export default { name : "LifeCycle" , created ( ) { console .log ("created: LifeCycle组件在内存中创建成功了" ) }, mounted ( ) { console .log ("mounted: LifeCycle组件第一次被渲染成功了" ) }, unmounted ( ) { console .log ("unmounted: LifeCycle组件被销毁了" ) }, } </script > <style scoped > </style >
上面的代码很好理解,在Left组件中对于LifeCycle组件是否展示设置了一个flag标志位来控制,如果不显示,设置为false,显示设置为true
当点击”显示LifeCycle组件”时,又会看到console控制台输出了”created: LifeCycle组件在内存中创建成功了”和”mounted: LifeCycle组件第一次被渲染成功了”,表示组件创建以及渲染成功,表示组件又再次被创建了以及渲染成功了,因为看到了LifeCycle组件的p标签的文字
2.7.2 监听组件的data数据更新
当组件的data数据更新以后,vue会自动重新渲染组件的DOM结构,从而保证view视图展示的数据和Model数据源一致,当组件重新被渲染完成后,会自动调用update函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <template> <LifeCycle v-if ="flag" > </LifeCycle > <button @click ="hiddenLifeCycle" > 点我隐藏LifeCycle组件</button > <button @click ="showLifeCycle" > 点我显示LifeCycle组件</button > </template> <script > import LifeCycle from "@/components/LifeCycle" ;export default { name : "Left" , components : { LifeCycle }, data ( ) { return { flag : true , } }, methods : { hiddenLifeCycle ( ){ this .flag = false }, showLifeCycle ( ){ this .flag = true }, }, updated ( ) { console .log ("Left组件的data发生了变化,自动调用了update函数" ) } } </script > <style scoped > button { margin-right : 15px ; } </style >
还是以2.7.1小节的Left和LifeCycle组件代码为例,仅在Left组件新增了update函数,因为我们在Left组件中定义了两个方法:hiddenLifeCycle和showLifeCycle,并且这两个方法对data数据源中的flag进行了修改,那么在页面上操作时,因为对data数据源进行了修改,那么Left组件就会自动调用update函数,就是我们在console控制台看到的内容
2.7.3 生命周期主要函数
生命周期函数
执行时机
所属阶段
执行次数
场景
created
组件在内容中创建完毕后
创建阶段
唯一1次
适合组件刚被创建就获取初始数据
mounted
组件初次在页面中渲染完毕后
创建阶段
唯一1次
操作DOM结构
updated
组件在页面中重新被渲染完毕后
运行阶段
0或多次
-
unmounted
组件在页面或内容中销毁后
销毁阶段
唯一1次
-
下图是vue官网的组件生命周期示意图:https://cn.vuejs.org/guide/essentials/lifecycle.html#lifecycle-diagram
2.8 组件关系 2.8.1 父向子传值 2.8.2 子向父传值 2.9 插槽 六、vue路由
前端路由指的就是Hash地址与组件之间的对应关系
不同组件之间的切换需要通过前端路由来实现
1、路由工作方式
路由变化过程:
用户点击了页面了的路由链接
导致了URL地址栏中的Hash值发生了变化
前端路由监听到了Hash地址的变化
前端路由把当前Hash地址对应的组件渲染到浏览器中
1.2 路由原理
使用锚链接模拟路由
使用锚点每次点击的时候,都会将a链接中的href中的路由拼接到浏览器地址栏的后面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <style > #b1 { background-color : pink; height : 120px ; } #b2 { background-color : red; height : 120px ; } #b3 { background-color : orange; height : 120px ; } </style > </head > <body > <a href ="#b1" > b1</a > <a href ="#b2" > b2</a > <a href ="#b3" > b3</a > <div id ='b1' > </div > <div id ='b2' > </div > <div id ='b3' > </div > </body > </html >
location这个属性可以拿到当前页面的链接以及Hash地址
location.href表示当前路由
location.hash表示hash地址,从地址栏的#号开始包含#号,表示hash地址
2、vue-router 2.1 vue-router介绍
vue-router是vue给出的路由解决方案,只能在vue项目中使用
vue-router的版本
2.2 vue-router4.x使用步骤
1、在项目中安装vue-router
1 npm i vue-router@next -S
2、定义路由组件
3、声明路由链接和占位符
使用<router-link>标签来声明路由链接(用来代替普通的a标签),并使用<router-view>标签来声明路由占位符,占位符就是需要在哪里展示组件,就声明到什么位置
使用<router-link>声明路由标签时,里面的to属性指定路由时,不需要显式的写成#/home,vue会自动给to属性的路由前面拼接#
1 2 3 4 5 6 7 8 9 10 11 12 13 <template> <div class ='app-box' > <p > 这是App根组件</p > <router-link to ="/home" > </router-link > <router-link to ="/goods" > </router-link > <router-link to ="/about" > </router-link > <router-view > </router-view > </div > </template>
4、创建路由模块
在项目中创建router.js路由模块,并在router.js中按照如下步骤创建获得路由的实例对象
从vue-router中按需导入两个方法
createRouter 方法适用于创建路由的实例对象
createWebHashHistory 用于指定路由的工作模式,hash模式
导入需要使用路由控制的组件
创建路由实例对象
向外共享路由实例对象
下面的文件名为router.js,可以放在component文件夹下,需要注意导入组件时的路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import { createRouter, createWebHashHistory } from "vue-router" import Home from './Home.vue' import Goods from './Goods.vue' import About from './About.vue' const router = createRouter ({ history : createWebHashHistory (), routes : [ { path : '/home' , component : Home }, { path : '/goods' , component : Goods }, { path : '/about' , component : About }, ], }) export default router
5、导入并挂载路由模块
在项目根目录下,vue2是main.js文件,vue3是main.ts文件,导入第4步创建的路由模块
然后按如下代码进行路由挂载
注意:
在vue3中,默认使用了typescript语法,所以在main.ts导入router时,会报错:”Could not find a declaration file for module ‘./components/router.js’. ‘xxxxx implicitly has an ‘any’ type.”
解决办法:在项目根目录下的tsconfig.json文件中的compilerOptions节点添加”noImplicitAny”: false,即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { createApp } from 'vue' import App from './App.vue' import Router from './components/router.js' const app = createApp (App )app.use (Router ) app.mount ('#app' )
2.3 路由重定向
重定向是指在访问地址A时,强制跳转至地址B,从而展示地址B组件的内容页面
通过路由规则的redirect属性,可以来指定一个新的路由地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import { createRouter, createWebHashHistory } from "vue-router" import Home from './Home.vue' import Goods from './Goods.vue' import About from './About.vue' const router = createRouter ({ history : createWebHashHistory (), routes : [ { path : '/' , redirect : '/home' }, { path : '/home' , component : Home }, { path : '/goods' , component : Goods }, { path : '/about' , component : About }, ], }) export default router
2.4 路由高亮
被激活的路由链接高亮有两种方式
被激活的路由链接,默认会应用一个叫router-link-active的类名,可以在编写高亮样式时,使用该类名选择器为激活的路由链接编写高亮样式
使用自定义的路由高亮class类
在路由文件中,也就是声明路由的位置,添加linkActiveClass属性,指定一个自定义的类名,就会替换掉默认的router-link-active类名
1 2 3 4 5 6 // 使用默认的`router-link-active`的类名编写高亮样式 .router-link-active { backgroud-color : red; color : white; font-weight : 700 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 创建路由实例对象中设置自定义的路由激活时使用的类名 const router = createRouter({ // 通过history指定路由工作模式 history: createWebHashHistory (), // 默认的router-link-active会被覆盖掉 linkActiveClass: 'router-class' , // 通过router数组指定路由规则 routes: [ { path: "/" , component: "" }, { path: "/pay-moon-box" , component: PayMoonBox }, ], });
2.5 嵌套路由 2.5.1 子路由声明
嵌套路由就是组件中嵌套组件再嵌套组件,那么最里面的组件的路由就是嵌套路由
如下图:
App组件中嵌套主页组件,主页组件嵌套列表组件
那么列表组件的路由就是:/home/list
那么列表组件的路由就是嵌套路由
那么如何声明嵌套路由呢?
使用children属性去声明嵌套的子路由
在children属性的path声明子路由时,官方推荐不在路由前面加/,直接写路由的内容即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const router = createRouter ({ history : createWebHashHistory (), routes : [ { path : "/home" , component : Home , children : [ { path :'list' , component : List }, ] } ], });
在router.js声明完路由以后,那就需要在Home组件中声明router-link和占位符router-view了
1 2 3 4 5 6 7 <template> <div > <router-link to ="/home/list" > 列表路由</router-link > <router-view > </router-view > </div > </template>
2.5.2 默认子路由
添加默认子路由有两种那个方式
在router.js文件中使用redirect属性,重定向到需要的路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const router = createRouter ({ history : createWebHashHistory (), routes : [ { path : "/home" , component : Home , redirect : '/home/list1' , children : [ { path :'list1' , component : List1 }, { path :'list2' , component : List2 }, ] } ], });
当进入/home路由时,默认进入/home/list1路由
2.6 动态路由
动态路由是指把Hash地址中可变部分定义为参数项,提高路由重复性使用
在vue-router中使用英文冒号(:)来定义路由的参数项
动态路由理解:
就是在跳转链接时,链接上有些参数可以动态拼接,比如:id
动态路由可以理解为后端路由上查询不同id时拼接的路由,只不过现在变为了前端也支持动态路由
2.6.1 动态路由配置
下面是在router中这是动态路由参数
1 2 3 4 5 6 7 8 9 10 const router = createRouter ({ history : createWebHashHistory (), routes : [ { path : "/goods/:id" , component : Goods } ], });
2.6.2 动态路由的参数获取
下面在组件中获取动态路由传递过来的参数和值
在通过动态路由匹配的方式渲染出来的组件中,可以使用$route.params对象访问到动态匹配的参数值
1 2 3 4 <template> <p > 动态参数id:{{ $route.params }} </p > </template>
下面是获取到的数据示例
2.6.3 使用props接收动态路由参数
为了便于获取路由参数,vue-router允许在路由规则中开启props传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { createRouter, createWebHashHistory } from "vue-router" import Left from "@/components/Left" ;const router = createRouter ({ history : createWebHashHistory (), routes : [ { path : "/left/:id" , component : Left , props : true } ], }); export default router
在路由指向的组件中使用props获取动态路由参数
1 2 3 4 5 6 7 8 export default { name : "Left" , components : { LifeCycle }, props : ['id' ], data ( ) {} }
使用props接收动态路由参数的好处
可以在路由指向的组件中使用this.的方式获取到动态路由参数,那么就可以将动态路由参数的值传给methos节点的函数里、生命周期函数里,这样通用性更强,
2.6.4 路径参数和查询参数获取
在vue组件中,在script节点下,可以使用this.$route获取到路径参数和查询参数,都是对象类型
1 2 3 4 5 <script> created ( ){ console .log (this .$route ); }, </script>
从上图可以看出,打印this.$route,可以看到
路径参数是在params这个对象中
查询参数是在query这个对象中
所以获取动态路由的参数时,按需获取就可以了
2.7 路由导航 2.7.1 声明式导航
通过点击链接跳转导航的方式,叫做声明式导航,比如:
2.7.2 编程式导航
通过调用API实现导航的方式,叫做编程式导航,比如:
普通网页调用location.href跳转到新页面的方式,叫做编程式导航
在vue-router提供了编程式导航的API
this.$router.push(‘hash地址’):跳转到指定Hash地址,从而展示对应的组件页面
this.$router.go(数值n):实现导航历史的前进、后退
2.7.3 $router.push
在组件中跳转另一个组件,使用编程式导航
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template> <p > 这是Left组件</p > <button @click ="jumpCycle" > 跳转至LifeCycle组件</button > </template> <script > export default { name : "Left" , components : {}, data ( ) { return { flag : true , } }, methods : { jumpCycle ( ){ console .log ("触发了:this.$router.push('/life-cycle') " ) this .$router .push ('/life-cycle' ) }, }, } </script > <style scoped > button { margin-right : 15px ; } </style >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <p > 这是LifeCycle组件</p > </template> <script > export default { name : "LifeCycle" , created ( ) { console .log ("created: LifeCycle组件在内存中创建成功了" ) }, mounted ( ) { console .log ("mounted: LifeCycle组件第一次被渲染成功了" ) }, unmounted ( ) { console .log ("unmounted: LifeCycle组件被销毁了" ) }, } </script > <style scoped > </style >
从下图看出,在Left组件的页面点击button跳转了到LifeCycle组件页面
2.7.4 $router.go
可以回退或者前进页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template> <p > 这是LifeCycle组件</p > <button @click ="goBack" > 回到Left组件</button > </template> <script > export default { name : "LifeCycle" , methods : { goBack ( ){ console .log ("触发了:this.$router.go(-1)" ) this .$router .go (-1 ) } }, created ( ) { console .log ("created: LifeCycle组件在内存中创建成功了" ) }, mounted ( ) { console .log ("mounted: LifeCycle组件第一次被渲染成功了" ) }, unmounted ( ) { console .log ("unmounted: LifeCycle组件被销毁了" ) }, } </script >
可以看出跳转到LifeCycle组件以后,点击”回到Left组件”按钮,又跳转回到了Left组件
2.8 导航守卫
导航守卫可以控制路由的访问权限,即对需要登录的路由访问时,需要先登录,登陆成功以后再去访问
2.8.1 声明全局导航守卫
全局导航守卫回拦截每个路由规则,并对每个路由进行访问权限的控制
1 2 3 4 5 6 7 8 const router = createRouter ({...});router.beforeEach (() => { console .log ("导航守卫函数..." ) })
2.8.2 全局守卫的3个形参
全局导航守卫可以接收3个形参,分别为to、from、next
to:表示目标路由对象,也就是页面当前访问的路由
form:当前导航正要离开的路由对象,也就是当前访问的路由的上一级路由是从什么
next:放行函数
如果在beforeEach的守卫访问方法中不声明next参数,那么beforeEach中不需要调用next方法,所有路由都可以放行
如果在beforeEach的守卫访问方法中声明了next参数,那么beforeEach就需要调用next方法对路由进行放行,否则不允许访问任何一个路由
1 2 3 4 5 6 7 8 router.beforeEach ((to, from , next ) => { console .log ("导航守卫函数..." ) console .log ('to:' , to) console .log ('from:' , from ) next () })
2.8.3 next函数的调用方式
直接全部放行:next()
强制让路由停留在当前页面:next(false)
强制让路由跳转到登录页面:next(‘/login’)
下面是示例,结合token展示最简单的路由守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 router.beforeEach ((to, from , next ) => { const token = localStorage .getItem ('token' ) if (to.path === "/left" && !token) { console .log ("未登录,跳转登录中..." ) next ('/login' ) } else { next () } })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template> <p > 登录页面</p > 用户名:<input type="text" v-model="username" > <button @click ="LoginFunc" > 登录</button > </template> <script > export default { name : "Login" , data ( ){ return { username : "" } }, methods : { LoginFunc () { localStorage .setItem ("token" , this .username ); } } } </script > <style scoped > </style >
下面是另外一个全局导航守卫的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 router.beforeEach ((to, from , next ) => { if ( to.path === '/login' ) return next () const token = localStorage .getItem ('token' ) if (!token) { console .log ("未登录,跳转登录中..." ) return next ('/login' ) } next () })