(部分图例引用黑马教程及其他文章来源)
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
}
以上渲染的结果是:
当 isActive 或者 hasError 变化时,class 列表将相应地更新。例如,如果hasError=true,则渲染结果为:
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'
}
以上结果渲染为:
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);
},
}
版权归原作者 离山港 所有, 如有侵权,请联系我们删除。