目录
1 vue的两个特性
1.1 数据驱动视图
1.2 双向数据绑定
2 MVVM工作原理
3 vue 的指令
3.1 内容渲染指令
3.2 属性绑定指令
3.3 事件绑定指令
3.4 事件修饰符
3.5 按钮修饰符
3.6 双向数据绑定指令
3.7 条件渲染指令
3.8 列表渲染指令
4 vue 的生命周期和生命周期函数
4.1 生命周期&生命周期函数
4.2 组件生命周期函数的分类
4.3 生命周期函数特点
5 keep-alive
5.1 keep-alive的基本使用
5.2 keep-alive属性
6 计算属性和侦听器
6.1 侦听器
6.1.1 作用
6.1.2 侦听器的格式
6.2 计算属性
6.2.1 使用
6.2.2 注意
6.3 Computed 和 Watch 的区别
7 获取组件/元素——refs
7.1 ref的概念
7.2 使用ref引用组件实例
8 绑定Class
8.1 对象语法:
8.1.1 传给v-bind:class一个对象,以动态切换class
8.1.2 在对象中传入更多字段来动态切换多个 class
8.1.3 绑定的数据对象不必内联定义在模板里
8.2 数组语法
8.2.1 把一个数组传给 v-bind:class,以应用一个 class 列表
8.2.2 根据条件切换列表中的 class
8.3 用在组件上
8.4 总结
9 绑定内联样式
9.1 对象语法
9.2 数组语法
10 组件通讯
10.1 父传子用props
10.2 子传父用$emit
10.3 兄弟组件之间的数据共享
11 插槽的使用
11.1 插槽基本用法
11.2 v-slot: 将内容放在指定插槽
11.3 插槽后背(默认)内容
11.4 具名插槽
11.5 作用域插槽
11.6 独占默认插槽的缩写语法
11.7 动态插槽名
11.8 带有 slot attribute 的具名插槽
12 路由的使用
12.1 前端路由的概念和原理
12.2 配置
12.3 把router对象挂载到main.js上
12.4 路由的基本使用
12.5 路由重定向
12.6 嵌套路由
12.7 动态路由匹配
12.8 捕获所有路由或 404 Not found 路由
12.9 编程式导航跳转
12.10 路由守卫
12.10.1 全局前置守卫
12.10.2 next 函数的 3 种调用方式
12.10.3 关于path和fullpath
12.11 路由传参
12.11.1 query传参
12.11.2 params传参
12.11.3 路由props配置
13 状态管理
13.1 状态管理简介
13.2 Vuex 的思想
13.3 核心状态管理
13.4 在项目中使用
13.4.1 store一般有以下6个文件
13.4.2 vuex中 this.$store.dispatch() 与 this.$store.commit()方法的区别
13.4.3 实例1
13.4.4 实例2
(部分图例引用黑马教程及其他文章来源)
1 vue的两个特性
1.1 数据驱动视图
数据的变化会驱动视图自动更新。
1.2 双向数据绑定
在网页中,form负责采集数据,Ajax负责提交数据。数据源的变化,会被自动渲染到页面上;页面上表单采集的数据发生变化的时候,会被 vue 自动获取到,并更新到数据源中。
2 MVVM工作原理
MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel,它把每个 HTML 页面都拆分成了这三个部分,如图所示:
注:
1、当数据源发生变化时,会被 ViewModel 监听到,VM会根据最新的数据源自动更新页面的结构。
2、当表单元素的值发生变化时,也会被VM监听到,VM会把变化过后最新的数据自动同步到Model数据源中。
3 vue 的指令
3.1 内容渲染指令
内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下 3 个:
- {{}}插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容!
(注意:插值表达式只能用在元素的内容节点中,不能用在元素的属性节点中!)
- v-text:会覆盖元素内部原有的内容!
- v-html:不仅会覆盖原来的内容,而且可以把带有标签的字符串,渲染成真正的HTML内容!
3.2 属性绑定指令
v-bind:为元素的属性动态绑定属性值,则需要用到 v-bind 属性绑定指令。
- 在 vue 中,可以使用v-bind:属性为元素的属性动态绑定值。
- 简写是英文的 " : "。
- 在使用 v-bind 属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号,例如:
<!--html-->
<a :href="'https://www.runoob.com/vue2/'+url">点击跳转vue菜鸟教程</a>
<!--script-->
const vm2=new Vue({
el:'#box2',
data:{
url:'vue-tutorial.html'
}
})
3.3 事件绑定指令
vue提供了v-on事件绑定指令,用于为DOM元素绑定事件监听。
- 注意:原生DOM对象有onclick、oninput、onkeyup等原生事件,替换为vue的事件绑定形式后,分别为v-on:click、v-on:input、v-on:keyup。
- 在v-on指令所绑定的事件处理函数,可以接收事件参数对象event。
- $event是vue提供的特殊变量,用来表示原生的事件参数对象event。
<button @click="add">自增</botton>
<button @click="changeColor">变色</botton>
data(){
return{
count:'',
}
}
methods:{
add(){
this.count++;
},
changeColor(e){
e.target.style.backgroundColor='red';
}
}
3.4 事件修饰符
3.5 按钮修饰符
3.6 双向数据绑定指令
3.7 条件渲染指令
- v-if、v-else、v-else-if条件性的渲染某元素,判定为true时渲染,否则不渲染
- 两者区别:v-if条件不满足不渲染,v-show条件不满足令其display为none
<div v-if="score<60">不及格</div>
<div v-else-if="60<=score&&score<90">中等</div>
<div v-else="score>=90">优秀</div>
<div v-show="true">display:block显示</div>
<div v-show="false">display:none隐藏</div>
3.8 列表渲染指令
注意:不推荐在同一元素上使用 v-if 和 v-for (详情请查看官网)
4 vue 的生命周期和生命周期函数
4.1 生命周期&生命周期函数
生命周期概念:生命周期是指一个组件从创建 > 运行 > 销毁的整个过程,强调的是一个时间段。
生命周期函数概念:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
(注意:生命周期强调的是时间段,生命周期函数强调的是时间点 。)
4.2 组件生命周期函数的分类
4.3 生命周期函数特点
<template>
<div class="test-container">
<h3 id="myh3">Test.vue 组件 --- {{ books.length }} 本图书</h3>
<p id="pppp">message 的值是:{{ message }}</p>
<button @click="message += '~'">修改 message 的值</button>
</div>
</template>
<script>
export default {
props: ['info'],
data() {
return {
message: 'hello vue.js',
books: []
}
},
watch: {
message(newVal) {
console.log('监视到了 message 的变化:' + newVal)
}
},
methods: {
},
beforeCreate() {
// 创建阶段的第1个生命周期函数。在这个函数无法访问data、prors、methods
//很少有
},
created() {
// 组件只是在内存中被创建好,但还未被渲染到页面
// 经常在它里面,调用 methods 中的方法,利用Ajax请求服务器的数据。并且,把请求到的数据,转存到 data 中,供 template 模板渲染的时候使用!
//可以访问data、prors、methods
this.initBookList()
},
beforeMount() {
//只是在内存上编译好HTML
//将在渲染组件时执行的操作
//很少用
},
mounted() {
//在此之前DOM还没被渲染,但此时在mounted()时DOM已经被渲染
// 如果要操作当前组件的 DOM,最早只能在 mounted 阶段执行
//组件创建阶段到此结束
},
beforeUpdate() {
//已经根据拿到最新数据,还没完成组件DOM结构的渲染
},
// 当数据变化之后,为了能够操作到最新的 DOM 结构,必须把代码写到 updated 生命周期函数中
updated() {
//已经根据最新数据,完成组件DOM结构的渲染。可以被执行多次(因为数据会变化多次)
//组件运行阶段到此结束
},
beforeDestroy() {
this.message = 'aaa'
},
destroyed() {
//组件销毁阶段到此结束
}
}
</script>
5 keep-alive
5.1 keep-alive的基本使用
当组件第一次被创建,会执行created生命周期函数,也会执行activated生命周期函数。之后组件再被激活,只会触发activated而不会触发created。
使用:
<keep-alive>
<组件名></组件名>
<keep-alive>
5.2 keep-alive属性
- include包含的组件(可以为字符串,数组,以及正则表达式,只有名称匹配的组件会被缓存)。
- exclude排除的组件(可以为字符串,数组,以及正则表达式,任何匹配的组件都不会被缓存)。
- max缓存组件的最大值(类型为字符或者数字,可以控制缓存组件的个数)。
// 只缓存组件name为a和b的组件
<keep-alive include="a,b">
<component />
</keep-alive>
// 组件name为c的组件不缓存(可以保留它的状态或避免重新渲染)
<keep-alive exclude="c">
<component />
</keep-alive>
// 如果同时使用include,exclude,那么exclude优先于include, 下面的例子只缓存a组件
<keep-alive include="a,b" exclude="b">
<component />
</keep-alive>
// 如果缓存的组件超过了max设定的值5,那么将删除第一个缓存的组件
<keep-alive exclude="c" max="5">
<component />
</keep-alive>
注意:若组件没有定义自己的name,则默认以注册组件时的名称作为匹配条件。如果定义了name,会以name作为匹配条件。
6 计算属性和侦听器
6.1 侦听器
6.1.1 作用
watch侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。
6.1.2 侦听器的格式
1、方法格式的侦听器(watch:{……}):
- 缺点1:无法在刚进入页面的时候自动触发。
- 缺点2:如果侦听的是一个对象,当对象里的属性值发生变化时,不会触发侦听器。
2、对象格式的侦听器 (watch(){……}):
- 好处1:可以通过immediate选项让侦听器自动触发。
- 好处2:可以通过deep选项,让侦听器深度监听对象中每个属性的变化。
如果要侦听对象里属性的变化,可以如以下操作:
6.2 计算属性
6.2.1 使用
1、定义计算属性:
new Vue({
el:"#app",
data:{ ... },
methods:{ ... },
watch:{ ... },
computed:{
计算属性名(){
计算过程
return 属性值
}
}
})
2、在页面上使用计算属性:
<p>{{计算属性名}}</p>
6.2.2 注意
1、computed 和 data同级,计算属性写在computed中;
2、写起来像方法,用起来像属性;
3、计算属性虽然称为属性,但其本质是一个函数;
4、虽然计算属性本质是一个函数,但是在页面中使用计算属性时,不要加();
5、一定要有返回值;
6、可以使用data中的已知值;
7、只要跟计算属性相关的数据发生了变化,计算属性就会重新计算,不相关的属性无论如何变化,都不会导致计算属性变化;
8、计算属性名不能和data中的数据重名(因为要使用data中的数据)。
【页面上使用】{{reversedMessage}}
【data中定义】msg:'New York'
【计算属性】computed:{
reversedMsg (){
return this.msg.split('').reverse().join('')
}
}
6.3 Computed 和 Watch 的区别
1、computed计算属性:
作用:
(1)解决模板中放入过多的逻辑会让模板过重且难以维护的问题。例如两个数据的拼接或字体颜色的判断。
(2)它支持缓存,只有依赖的数据发生了变化,才会重新计算。例如模板中多次用到数据拼接可以用计算属性,只执行一次计算,除非数据发生变化。
(3)不支持异步,如果有异步操作,无法监听数据的变化。
(4)如果属性值是函数,默认使用get方法,函数的返回值就是属性的属性值。还有一个set方法,当数据变化时就会调用set方法。
(5)computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。
2、watch侦听器:
作用:
(1)它不支持缓存,数据变化时,它就会触发相应的操作。
(2)支持异步监听。
(3)接受两个参数,第一个是最新的值,第二个是变化之前的值。
(4)监听data或者props传来的数据,发生变化时会触发相应操作。有两个参数:
immediate:立即触发回调函数。
deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。
3、总结:
(1)computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
(2)watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。
4、computed与watch的使用场景:
computed:是多对一,多个数据影响一个。当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
watch:是一对多,一个数据发生变化,执行相应操作会影响多个数据。当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
————————————————
版权声明:本板块为CSDN博主「前端路啊」的原创文章,原文链接:原文网址
7 获取组件/元素——refs
7.1 ref的概念
ref用来辅助开发者在不依赖于jQuery的情况下,获取DOM元素或组件的引用。
<p ref="mytext">我会被refs获取到</p>
<button @click="refTest">获取mytext,改变其文本</button>
refTest(){
console.log(this.$refs.mytext);
this.$refs.mytext.innerHTML='我被获取到啦'
},
7.2 使用ref引用组件实例
【子组件 child.vue】
showTitle(){
alert('aaa');
}
【父组件 parent.vue】
<child ref="A">流程环节配置</child>
<button @click="B">点我弹出</button>
B(){
this.$refs.A.showTitle();
},
8 绑定Class
8.1 对象语法:
8.1.1 传给v-bind:class一个对象,以动态切换class
<template>
<div>
<!-- class和style -->
<div v-bind:class="{ 'active': isActive, 'text-danger': hasError }">aa</div>
</div>
</template>
<script>
export default {
data() {
return {
isActive:true,
hasError:false
}
},
}
</script>
<style>
.active{
color:#e5c
}
.text-danger{
color: rgb(16, 212, 65);
}
</style>
上面的语法表示 active 这个 class 存在与否将取决于数据isActive为true还是false。
8.1.2 在对象中传入更多字段来动态切换多个 class
此外,v-bind:class 指令也可以与普通的 class attribute 共存。当有如下模板:
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
data: {
isActive: true,
hasError: false
}
以上渲染的结果是:<div class="static active"></div>
当 isActive 或者 hasError 变化时,class 列表将相应地更新。例如,如果hasError=true,则渲染结果为:<div class="static active hasError"></div>
8.1.3 绑定的数据对象不必内联定义在模板里
<div v-bind:class="classObject"></div>
data: {
classObject: {
active: true,
'text-danger': false
}
}
8.2 数组语法
8.2.1 把一个数组传给 v-bind:class,以应用一个 class 列表
<div v-bind:class="[class1,class2]"></div>
data: {
class1:'active',
class2:'box'
}
以上结果渲染为:<div class="active box"></div>
8.2.2 根据条件切换列表中的 class
在数组语法中也可以使用对象语法:
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
这样写将始终添加 errorClass,但是只有在 isActive:true 时才添加 activeClass。
8.3 用在组件上
对于带数据绑定 class的组件也同样适用:
<my-component v-bind:class="{ active: isActive }"></my-component>
当 isActive 为 true 时,class="组件原来的样式 active"
8.4 总结
方式一:v-bind:class="{ '类名1': data1, '类名2': data2,…… }"
data(){
return{
data1:false/true,
data2 : false/true
……
}
}
方式二:v-bind:class="对象名"
data(){
return{
对象名:{
"类名1":false/true,
"类名2":true/true,
……
}
}
}
方式三:v-bind:class="[class1,class2,class3,……]"
data(){
return{
class1:'类名1',
class2:'类名2',
……
}
}
9 绑定内联样式
9.1 对象语法
v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:
<div v-bind:style="{color:textColor,fontSize:textSize+ 'px'}">aa</div>
data() {
return {
textColor:'#aa8',
textSize:30
}
},
直接绑定到一个样式对象通常更好,这会让模板更清晰:
<div v-bind:style="styleObject"></div>
data: {
styleObject:{
color:'#fff',
fontSize:25+'px',
}
}
9.2 数组语法
v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:
<div v-bind:style="[styleObject1,styleObject2]"></div>
data: {
styleObject1: {
color: 'red',
fontSize:13+'px'
},
styleObject2: {
width: 100+'px',
height:130+'px'
}
}
10 组件通讯
10.1 父传子用props
- 第一步:引入子组件。
- 第二步:在数据源中定义要传入子组件的数据parentMsg。
- 第三步:在使用child组件时传入parentMsg。<child :自定义属性名="parentMsg"></child>。
- 第四步:在子组件中,要 props:['自定义属性名']来接收传过来的参数。
【父组件】
<template>
<div>
<h2>parent</h2>
<!--3、传入parentMsg-->
<child :visible="visible"></child>
</div>
</template>
<script>
//1、引入子组件
import child from './child.vue'
export default {
data() {
return {
//2、定义要传入子组件的数据parentMsg
visible:'true'
}
},
components:{
child
}
}
</script>
【子组件】
<template>
<div>
{{visible}}
</div>
</template>
<script>
export default {
name:'child',
//使用prors对象可以设置配置项,使用prors数组不可以。
// props:{
// parentMsg:{
// type:String,
// default:'i am child'
// }
// }
props:['visible']//接收
}
</script>
10.2 子传父用$emit
emit使用方法:this.$emit(‘自定义事件名’,所需要传的值)
- 第一步:首先在子组件中定义一个事件,并且使用emit发送给父组件,在示例中子组件使用的click事件触发发送自定义事件(sendmsg)。
- 第二步:在父组件中需要定义方法(getmsg)接受自定义事件(sendmsg):
- 第三步:在使用子组件时,<child @sendmsg="getmsg"></child>。
【子组件】发送值
<template>
<div>
<button @click="childmsg">点我试试</button>
</div>
</template>
<script>
export default {
name:'child',
data() {
return {
msg:"This is the first word from child"
}
},
methods:{
//点击按钮则向父组件传自定义事件sendmsg,childmsg
childmsg(){
this.$emit('sendmsg',this.msg)
}
}
}
</script>
【父组件】接收值
<template>
<div>
<child @sendmsg="getmsg"></child>
</div>
</template>
<script>
import child from './child.vue'
export default {
data() {
return {
}
},
components:{
child
},
methods:{
getmsg(val){
console.log(val)
}
}
}
</script>
总结:
- 父传子【父中定义要传的值A,通过:B="A"传给子,子用props:["B"]接收,但props只读,要C:this.B,在子组件中就可以对C进行操作(可以理解为B只是C的初始值)】
- 子传父【在子中的某个事件 this.$emit('传给父组件的事件E',要传的值G),父用@E="F"接收并使用,在F中可以如下定义:F(val){……},此处会把G作为实参传给val】
10.3 兄弟组件之间的数据共享
- 第一步:在兄弟组件同目录下创建eventBus.js,然后创建vue实例:
import Vue from 'vue'
export default new Vue()
-
第二步:在【兄弟组件A】中,引入eventBus.js > 定义数据msg > 编写方法用于发送msg:
import bus from './eventBus.js'
<button @click="sendMsg">
export default{
data(){
return{
msg:'hello'
}
},
methods:{
sendMsg(){
bus.$emit('share',this.msg);
}
}
}
- 第三步:在【兄弟组件B】中,引入eventBus.js>定义数据newMsg>编写方法用于接收msg和赋值给newMsg:
import bus from './eventBus.js'
<button @click="sendMsg">
export default{
data(){
return{
newMsg:[]
}
},
created:{
bus.$on('share',val=>{
this.newMsg=val;
})
}
}
11 插槽的使用
总体思想:父组件指定内容,子组件渲染内容。
11.1 插槽基本用法
【父组件】
<Left>
<p>这是在 Left 组件声明的p标签</p>
</Left>
【子组件】
<div class="left-box">
<span>Left 组件</span>
<!-- 在 Left 组件内声明一个插槽区 -->
<slot></slot>
</div>
11.2 v-slot: 将内容放在指定插槽
- vue 官方规定:每一个 slot 插槽都要有一个 name 名称
- 如果省略了 slot 的name 则有一个默认名称 default
- 默认情况下,使用组件时提供的内容会被填充到 name 为 default 的插槽内
【将内容放在指定的插槽内】:
- 使用 v-slot:xxx, 其中 xxx为插槽 name 值,只能放在 标签内
- 使用 标签包裹
- 是一个虚拟标签,只起到包裹性质的作用,不会被渲染为实质性的 html 元素
- v-slot:xxx 可以简写为 #xxx
【父组件】
<Left>
<template v-slot:mySlot>
<p>这是在 Left 组件声明的p标签</p>
</template>
</Left>
【子组件】
<div style="color:#33e;background:#ee2">
<slot name="mySlot"></slot>
</div>
11.3 插槽后背(默认)内容
- 当使用组件指定了插槽内容时,优先显示指定的内容
- 当没有指定内容时,渲染 slot 标签内的默认内容
11.4 具名插槽
当需要将内容置入不同组件时,要用带有name属性的插槽:
<HeaderVue #header>我是来自header的插槽</HeaderVue>
<Main>
<template v-slot:box1>我是来自child的插槽box1</template>
<template v-slot:box2>我是来自child的插槽box2</template>
</Main>
- 注意 v-slot 只能添加在<template>上
- v-slot 也有缩写,即把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header
11.5 作用域插槽
条件:在封装组件时,为预留的 <slot> 提供属性对应的值
格式:
子组件:<slot v-bind:username='username' name='box1'></slot>
父组件:<template v-slot:box1='username_prors'>
在封装组件时,为预留的 <slot> 提供属性对应的值,叫做作用域插槽。这些属性对应的值可以在父组件中访问到,默认为空对象。
【子组件中】
<div>
<slot v-bind:user="user" name="box3"></slot>
<slot v-bind:msg="hello world" name="box4"></slot>
</div>
data(){
return:{
user:{
firstname:'lan',
lastname:'chun'
}
}
}
【父组件中】
<子组件名>
<template v-slot:box3="slotProps1">
{{slotProps1.user.firstname}}
{{slotProps1.user.lastname}}
</template>
<template v-slot:box4="slotProps2">
{{slotProps2.msg}}
</template>
</子组件名>
11.6 独占默认插槽的缩写语法
条件:被提供的内容只有默认插槽时
格式:直接写在组件里
当被提供的内容只有默认插槽时,组件的标签可以被当作插槽的模板来使用,可以把 v-slot 直接用在组件上:
<子组件名 v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</子组件名>
- 当被提供的内容只有默认插槽时,可以用 v-slot:default=" ",也可以直接用缩写v-slot=" "。
- 默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确。
- 只要出现多个插槽,请始终为所有的插槽使用完整的基于 的语法,如下:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
11.7 动态插槽名
动态指令参数也可以用在 v-slot 上,来定义动态的插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
11.8 带有 slot attribute 的具名插槽
直接把 slot attribute 用在一个普通元素上:
<base-layout>
<h1 slot="header">Here might be a page title</h1>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<p slot="footer">Here's some contact info</p>
</base-layout>
12 路由的使用
url地址里,‘#’及以后的部分称为哈希地址,可以在控制台用location.hash打印哈希地址
12.1 前端路由的概念和原理
1)用户点击页面上的路由链接
2)导致url地址的Hash值变化
3)前端路由监听到Hash地址的变化
4)前端路由把当前Hash地址对应的组件渲染到浏览器中
12.2 配置
- 第一步:安装vue-router包npm install vue-router@3.5.2 -S
- 第二步:创建路由模块。创建router文件夹,在文件夹下建立index.js
//1、导入Vue和VueRouter的包,定义 (路由) 组件
import Vue from 'vue'
import VueRouter from 'vue-router'
import Prize from '@/components/Prize.vue'
import Home from "@/components/Main.vue";
import parent from '@/components/parent'
//2、调用Vue.use()函数,把VueRouter安装为vue插件
Vue.use(VueRouter);
//3、创建路由的实例对象
const router =new VueRouter({
routes:[
{
path:'/home',
component:Home
},
{
path:'/prize',
component:Prize,
},
{
path:'/parent',
component:parent
}
]
})
//4、向外共享路由的实例
export default router
12.3 把router对象挂载到main.js上
(在new Vue中的router是“router:router”的简写,若import routerVue from 'xxxx',则应写为router:routerVue)
( "el"等价于$mount)
12.4 路由的基本使用
- 第一步:在index.js中定制路由规则:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Prize from '@/components/Prize.vue'
import Home from "@/components/Main.vue";
import parent from '@/components/parent'
Vue.use(VueRouter);
const router =new VueRouter({
routes:[
{
path:'/',
redirect:'/home'
},
{
path:'/home',
component:Home
},
{
path:'/prize',
component:Prize,
},
{
path:'/parent',
component:parent
}
]
})
export default router
- 第二步:在页面中使用router-view:
<!--可以实现路由跳转,如下:-->
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/home">首页</router-link>
<router-link to="/prize">奖品</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
//也可以在methods中使用,如下:
methods: {
goBack() {
window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/')
}
}
- 在进行模块化导入的时候,如果给定的是文件夹,则默认导入这个文件夹下,名字叫做 index.js 的文件
- 当对应的路由匹配成功,将自动设置 class 属性值 。查看API文档学习更多相关内容。
12.5 路由重定向
12.6 嵌套路由
需求:在apptest.vue下路由到about,在about组件下路由到tab1、tab2
- 第一步:路由配置index.js
{
path: '/about',
component: About,
// redirect: '/about/tab1',
children: [
// 子路由规则
// 默认子路由:如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则,叫做“默认子路由”
{ path: '', component: Tab1 },
{ path: 'tab2', component: Tab2 }
]
},
- 第二步:在apptest.vue下路由到about
<router-link to="/about">关于</router-link>
<!-- 作用很单纯:占位符,给要显示的组件预留位置的 -->
<router-view></router-view>
- 第三步:在about组件下路由到tab1、tab2
<router-link to="/about">关于</router-link>
<router-link to="/about/tab2">关于</router-link>
<router-view></router-view>
12.7 动态路由匹配
需求:有一个组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。
const User = {
template: '<div>User</div>'
}
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
//相当于/user?id=xxx
]
})
①可以通过const User = {template: '{{ $route.params.动态路径参数 }}'来查看当前用户的动态路径参数。
打印this时是对象
this.$route 是路由的“参数对象”
this.$router 是路由的“导航对象”
②可以为路由规则开启 props 传参,从而方便的拿到动态参数的值
【index.js】
{ path: '/movie/:mid', component: Movie, props: true },
【movie.vue】
props: ['mid'],
- 在 hash 地址中, / 后面的参数项,叫做“路径参数”
- 在路由“参数对象”中,需要使用 this.$route.params 来访问路径参数
- 在 hash 地址中,? 后面的参数项,叫做“查询参数”
- 在路由“参数对象”中,需要使用 this.$route.query 来访问查询参数
- 在 this.$route 中,path 只是路径部分;fullPath 是完整的地址
【 例如】:
/movie/2?name=zs&age=20 是 fullPath 的值
/movie/2 是 path 的值
12.8 捕获所有路由或 404 Not found 路由
{
// 会匹配所有路径
path: '*'
}
{
// 会匹配以 `/user-` 开头的任意路径
path: '/user-*'
}
注:当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: '*' } 通常用于客户端 404 错误。
12.9 编程式导航跳转
vue-router中的编程式导航API,常用的导航API有:
①this.$router.push('hash地址')
跳转到指定hash地址,并增加一条历史记录
②this.$router.replace('hash地址')
跳转到指定的hash地址,并替换掉当前的历史记录
③this.$router.go(数值n)
跳回第n条历史记录
<template>
<div class="movie-container">
<button @click="gotoLk">通过 push 跳转到“洛基”页面</button>
<button @click="gotoLk2">通过 replace 跳转到“洛基”页面</button>
<button @click="goback">后退</button>
<!-- 在行内使用编程式导航跳转的时候,this 必须要省略,否则会报错! -->
<button @click="$router.back()">back 后退</button>
<button @click="$router.forward()">forward 前进</button>
</div>
</template>
<script>
export default {
name: 'Movie',
methods: {
gotoLk() {
// 通过编程式导航 API,导航跳转到指定的页面
this.$router.push('/movie/1')
},
gotoLk2() {
this.$router.replace('/movie/1')
},
goback() {
// go(-1) 表示后退一层
// 如果后退的层数超过上限,则原地不动
this.$router.go(-1)
}
}
}
</script>
12.10 路由守卫
路由守卫可以控制路由的访问权限。
12.10.1 全局前置守卫
【index.js】中
// 为 router 实例对象,声明全局前置导航守卫
// 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数
router.beforeEach(function(to, from, next) {
// to 表示将要访问的路由的信息对象
// from 表示将要离开的路由的信息对象
// next() 函数表示放行的意思
// 分析:
// 1. 要拿到用户将要访问的 hash 地址
// 2. 判断 hash 地址是否等于 /main。
// 2.1 如果等于 /main,证明需要登录之后,才能访问成功
// 2.2 如果不等于 /main,则不需要登录,直接放行 next()
// 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
// 3.1 如果有 token,则放行
// 3.2 如果没有 token,则强制跳转到 /login 登录页
if (to.path === '/main') {
// 要访问后台主页,需要判断是否有 token
const token = localStorage.getItem('token')
if (token) {
next()//如果有token(登录)就放行
} else {
// 没有登录,强制跳转到登录页
next('/login')
}
} else {
next()
}
})
12.10.2 next 函数的 3 种调用方式
12.10.3 关于path和fullpath
如:http://localhost:8080/index?page=1
fullPath:路由全地址,fullPath为/index?page=1
path:路径,不带参数,path为/index
12.11 路由传参
12.11.1 query传参
1. 路由跳转并携带query参数,to的字符串写法 messageData是一个变量
<router-link :to="`/home/news?id=001&message=${messageData}`" ></router-link>
2. 路由跳转并携带query参数,to的对象
<router-link :to="{
path:"/home/news",
query:{
id:001,
message:messageData
}
}" >
</router-link>
获取参数:this.$route.query.id 、 this.$route.query.message
12.11.2 params传参
方式一:路由跳转并携带param参数,to的字符串写法 ,首先我们要在路由文件中定义我们要传递的参数
// 1. 路由跳转并携带params参数,to的字符串写法 messageData是一个变量
<router-link :to="`/home/news/001/${messageData}`" ></router-link> //即{id:001,message:xxx}
跳转时直接斜杠/后面拼接参数
// 1. 路由跳转并携带params参数,to的字符串写法 messageData是一个变量
<router-link :to="`/home/news/001/${messageData}`" ></router-link> //即{id:001,message:xxx}
方式二:路由跳转并携带params参数,to的对象写法,不需要在路由文件中定义参数
<router-link :to="{
name:"HomeNews", //使用params传参时,必须使用name属性进行路由跳转,不能使用path配置项跳转
params:{
id:001,
message:messageData
}
}" ></router-link>
获取参数:this.$route.params.id 、 this.$route.params.message
12.11.3 路由props配置
传参配置: src/router/index.js
{
name:'HomeNews'
path:'news/:id/:message',//二级路由,定义参数,表示第一个参数是id,第二个是message
component:News,
// 第一种写法:props值为对象,该对象中所有的key-value最终都会通过props传递给组件news
// props:{a:1},
// 第二种写法(只能params):props值为Boolean,为true时把路由收到的`params`参数通过props传递给组件news
// props:true,
// 第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传递给组件news
props:function(route){
return {
id:route.query.id,
message:route.query.message
}
},
},
使用: New.vue
export default{
prors:['id','message']
}
13 状态管理
13.1 状态管理简介
vuex是专为vue.js应用程序开发的状态管理模式。它采用集中存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。vuex也集成刀vue的官方调试工具devtools extension,提供了诸如零配置的time-travel调试、状态快照导入导出等高级调试功能。
13.2 Vuex 的思想
当我们在页面上点击一个按钮,它会触发(dispatch)一个action, action 随后会执行(commit)一个mutation, mutation 立即会改变state, state 改变以后,我们的页面会state 获取数据,页面发生了变化。
13.3 核心状态管理
通俗理解是存储在store里的都是全局变量,可以通过方法提交更新,其他页面和组件也会同步更新,拿到最新的值。状态管理核心状态管理有5个核心,分别是state、getter、mutation、action以及module。
- state
驱动应用的数据源。state为单一状态树,可以使用一个对象包含全部的应用层级状态,state就是数据源。
- getter
getter有点类似vue的计算属性computed,当我们需要对数据进行处理,那么我们就需要使用getter,getter会接收state作为第一个参数,而且getter的返回值会根据它的依赖被缓存起来,只有getter中的依赖值(state中的某个需要派生状态的值)发生改变的时候才会被重新计算。
const getters = {
getUserState (state) {
let data;
if(state.userState==0){
data='无效'
}else if(state.userState==1){
data = '1级'
}else{
data = '2级'
}
return data;
}
}
export default new Vuex.Store({
state,
mutations,
getters
})
页面上使用这个getters:
计算数据状态:{{$store.getters.getUserState}}
- mutation
更改store中state状态的唯一方法就是提交mutation,类似vue中的methods。每个mutation都有一个字符串类型的事件类型和一个回调函数,我们需要改变state的值就要在回调函数中改变。我们要执行这个回调函数,那么我们需要执行一个相应的调用方法:store.commit。
- action
响应在 view 上的用户输入导致的状态变化。action可以提交mutation,在action中可以执行store.commit,而且action中可以有任何的异步操作。action处理异步操作,由于mutation都是同步事务,在 mutation 中混合异步调用会导致你的程序很难调试。action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。
mutations: {
addAge: (state, payload) => {
state.informations.forEach(info => {
info.age += payload;
})
}
},
actions: {
addAge: (context, payload) => {
setTimeout(function () {
context.commit("addAge", payload);
}, 2000);
}
}
在组件的methods中能够通过【this.$store.dispatch("addAge", 2);】分发action。
(或者使用mapActions辅助函数将组件的 methods 映射为 store.dispatch 调用)
methods: {
addAge() {
this.$store.dispatch("addAge", 2);
}
},
注意:
1、所有 store 中 state 的变更,都放置在 store 自身的 action 中去管理。
2、Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到更新。
3、不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交(commit)mutation。
13.4 在项目中使用
13.4.1 store一般有以下6个文件
13.4.2 vuex中 this.$store.dispatch() 与 this.$store.commit()方法的区别
这两个的区别是存取的方式不同,两个都是传值给vuex的mutation改变state.
- this.$store.dispatch含有异步操作,可以向后台提交数据
【存储】this.$store.dispatch("action的方法名",value)
【取值】this.$store.getters.action的方法名
- this.$store.commit同步操作
【存储】this.$store.commit("mutation的方法名",value)
【取值】this.$store.state.mutation的方法名
13.4.3 实例1
在app.vue里先import store from “./store” 并且在new vue实例的时候加上store,这样就可以全局调用了。
【src/store/index.js】引入Vuex文组件:
//第一步:使用import引入vue和vuex
import Vue from 'vue';
import Vuex from 'vuex';
//第二步:把vuex作为组件引入
Vue.use(Vuex);
//第三步:实例化vuex.store对象
//store变量是实例化一个vuex.store
export const store =new Vuex.Store({
//第四步:定义state
//state专门用于保存共享的状态值
state:{
//保存登陆状态
login:false
},
//第五步:编写方法改变state中的值
//专门书写方法,用于更新state中的值
mutations:{
doLogin(state){
state.login=true;
},
doLoginout(state){
state.login=false;
}
}
});
【 src/components/Header.vue】组件JS部分:
<script>
//使用vuex的mapState需要引入
import {mapState} form "vuex";
export default{
name:"Header",
//引入vuex>store>state中的值,必须在计算属性中书写
computed:{
//mapState辅助函数,可以快速引入store中的值
...mapState(["login"])
},
methods:{
loginout(){
//调用store里登出的方法
this.$store.commit(" doLoginout");
}
}
}
</script>
【src/components/Login.vue】组件JS部分 :
<script>
export default {
name: "Login",
data() {
return {
};
},
methods: {
doLogin() {
……
// 路由跳转指定页面
this.$router.push({ path: "/" });
//更新 vuex 的 state的值, 必须通过 mutations 提供的方法才可以
//通过 commit('方法名') 就可以触发mutations 中的指定方法
this.$store.commit("doLogin");
}
});
}
}
};
</script>
使用$router.push转到首页url,并且调用store里的登录方法把共享登录状态变成true。
13.4.4 实例2
【声明】src/store/index.js:
import Vuex from 'vuex';
import Vue from 'vue';
……;
import test from '@/store/modules/test'
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
……,
test
},
});
【定义】src/store/models/test.js:
const state={
username:'lanchun',
userState:0
}
const mutations={
SetUserName(state,name){
state.username=name;
},
SetUserState(state,num){
state.userState+=num;
}
}
export default{
state,
mutations
}
【使用】src/views/page.vue:
<template>
<div>
<h1>{{$store.state.test.username}}</h1>
<h1>{{$store.state.test.userState}}</h1>
数据状态:
<a-button @click="addState">状态+1</a-button>
</div>
</template>
methods: {
addState(){
console.log(this.$store.state.test);
console.log(this.$store);
this.$store.commit('SetUserName','张三');
this.$store.commit('SetUserState',1);
},
}