目录
1.3.3编写上一步创建的index.js/store.js,并在main.js入口文件中引入
1.7Vuex的mapState()与mapGetters()
1.7.1为什么用vuex的mapState()函数/mapGetters()函数?
1.7.2在需要用到mapState/mapGetters的组件中引入(前提是组件所属工程的main.js已经引如并配置了Vuex):
1.7.4mapState()、mapGetters()函数返回值和...mapStatte(['sum','school','subject'])语法
1.7.5mapState()/mapGetters()生成的计算属性在开发者工具中标题是vuex bindings
1.8vuex的mapActions()与mapMutations()
2.1.1创建router文件夹(和components目录、vuex的store目录、main.js、App.vue平级):
2.1.4App.vue:(样式是public文件夹中的bootstrap.css中的)
2.3.1路由的query参数(Message.vue向子路由组件Detail.vue传参)
2.4.2第二种写法,值为布尔,只对params方式传参有效对query方式无效
2.4.3第三种写法,值为函数,对params、query方式传参均有效
2.8.2activated()、deactivated()
2.9.1全局前置路由守卫--$router.beforeEach((to,from,next)=>{})
2.9.2全局后置路由守卫--$router.afterEach((to,from)=>{})
2.10独享路由守卫--beforeEnter:(to,from,next)=>{}
2.11组件内路由守卫(进入组件前路由守卫/离开组件前路由守卫)--beforeRouteEnter(to,from,next)/beforeRouteLeave(to,from,next)
1.Vuex
1.1初识Vuex
1.1.1理解Vuex
1.1.2全局事件总线和Vuex对比
1.2Vuex工作原理图
图1:
图2:
图3:
1.3搭建Vuex环境
2022.02.07,Vue3成为了Vue默认版本,npm i vue安装的直接就是Vue3了,与此同时,Vuex也更新到了Vuex4,npm i vuex安装的直接就是Vuex4了,Vuex4只能在Vue3中使用。Vue2中只能使用Vuex3版本。
1.3.1安装Vuex3版本
npm i vuex@3
1.3.2创建文件夹和文件
官方推荐方式:这样也可以:
1.3.3编写上一步创建的index.js/store.js,并在main.js入口文件中引入
正确方式:
main.js中:
index.js中:
vm和所有的vc身上都会有$store属性:
错误方式:
1.4Vuex求和案例
main.js:
//引入Vue import Vue from 'vue' //引入App import App from './App.vue' //引入插件 import vueResource from 'vue-resource' //引入store import store from './store' //关闭Vue的生产提示 Vue.config.productionTip = false //使用插件 Vue.use(vueResource) //创建vm new Vue({ el:'#app', render: h => h(App), store, beforeCreate() { Vue.prototype.$bus = this } })
App.vue:
<template> <div> <Count/> </div> </template> <script> import Count from './components/Count' export default { name:'App', components:{Count}, mounted() { // console.log('App',this) }, } </script>
Vuex的index.js:
//该文件用于创建Vuex中最为核心的store import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) //准备actions——用于响应组件中的动作 const actions = { //没有业务逻辑,Count.vue组件可以省略Dispatch直接commit到mutations //第一个参数是mini版的Store对象,具备Store的一部分属性如state。(也有Store没有的属性) /* jia(context,value){ console.log('actions中的jia被调用了') context.commit('JIA',value) }, jian(context,value){ console.log('actions中的jian被调用了') context.commit('JIAN',value) }, */ jiaOdd(context,value){ console.log('actions中的jiaOdd被调用了') //【context对象身上也有state对象和dispatch方法, //dispatch方法用来在actions中调actions方法, //state对象主要用来做判断等逻辑(一般不直接在action中运算state数据,而在mutation中专门操作数据)】 if(context.state.sum % 2){ context.commit('JIA',value) } }, jiaWait(context,value){ console.log('actions中的jiaWait被调用了') setTimeout(()=>{ context.commit('JIA',value) },500) } } //准备mutations——用于操作数据(state) const mutations = { //mutations中的方法名一般全大写,方法体中不要写业务逻辑,直接操作数据 //【在actions中一般不操作数据,一般在mutation中运算数据, //因为只有mutation动作才会被vuex开发者工具记录到】 //第一个参数是state对象 JIA(state,value){ console.log('mutations中的JIA被调用了') state.sum += value }, JIAN(state,value){ console.log('mutations中的JIAN被调用了') state.sum -= value } } //准备state——用于存储数据 const state = { sum:0 //当前的和 } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
Count.vue:
<template> <div> <!-- 引入并应用Vuex并在main.js中写store配置项后, vm和所有vc身上就都有$store属性 --> <h1>当前求和为:{{$store.state.sum}}</h1> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementOdd">当前求和为奇数再加</button> <button @click="incrementWait">等一等再加</button> </div> </template> <script> export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, methods: { increment(){ //没有业务逻辑可直接commit调用mutations中方法 this.$store.commit('JIA',this.n) }, decrement(){ this.$store.commit('JIAN',this.n) }, incrementOdd(){ this.$store.dispatch('jiaOdd',this.n) }, incrementWait(){ this.$store.dispatch('jiaWait',this.n) }, }, mounted() { console.log('Count',this) }, } </script> <style lang="css"> button{ margin-left: 5px; } </style>
v-model.number等同v-model:value.number,
此外,请留意(百度)select标签的其它用法:
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
补充:actions中的方法可以只有一个context参数,也可以有context参数和value两个参数,但是不能有三个及以上参数!
1.5Vuex开发者工具
Vuex的开发者工具和Vue开发者工具在同一块地方。
1.6Vuex的getters配置项
vuex的getters配置项类似vue的data配置项,但data配置项不能跨组件使用,而getters配置项可以跨组件使用。
在vuex的index.js文件中追加getters配置项:
在组件中使用getters数据:
在vuex开发者工具的Filter inspected state块中,除了可以看到state数据、mutation动作,还可以看到getters配置项中的属性:
1.7Vuex的mapState()与mapGetters()
1.7.1为什么用vuex的mapState()函数/mapGetters()函数?
因为存在冗余代码,用来减少冗余代码。
1.7.2在需要用到mapState/mapGetters的组件中引入(前提是组件所属工程的main.js已经引如并配置了Vuex):
1.7.3语法概括:
1.7.4mapState()、mapGetters()函数返回值和...mapStatte(['sum','school','subject'])语法
上图说明mapState、mapGetters不仅可用在computed块、也可用在mounted钩子等其它地方。
1.7.5mapState()/mapGetters()生成的计算属性在开发者工具中标题是vuex bindings
1.8vuex的mapActions()与mapMutations()
1.8.1为什么使用?可减少冗余代码。
1.8.2在需要用到的组件中引入:
1.8.3使用方式及注意事项:
1.9多组件共享数据
视频p114集,过于简单且没有新意,略过。
多组件共享数据:多组件通过vuex共享一套vuex中的state/getters数据。例如p114集中所讲的Count组件可以改变vuex中的state中的sum的值,Person组件也用到了vuex中的state中的sum,Person组件在界面的展示的sum的值会随着Count组件对sum的更新而更新,两者实现联动即共享数据。
1.10Vuex模块化+namespace
namespaced:true的作用
如果不配置namespaced:true,就不能使用下面的写法:
例如有如下配置:
如此配置完之后,
可以看到$store.state有属性a、b,同理$store.actions/$store.mutations也会有。
注意不是$store有属性a、b!
可以多个模块在一个文件:
也可以多个模块在多个文件,由index.js引用:
index.js:
//该文件用于创建Vuex中最为核心的store import Vue from 'vue' //引入Vuex import Vuex from 'vuex' import countOptions from './count' import personOptions from './person' //应用Vuex插件 Vue.use(Vuex) //创建并暴露store export default new Vuex.Store({ modules:{ countAbout:countOptions, personAbout:personOptions } })
count.js:
//求和相关的配置 export default { namespaced:true, actions:{ jiaOdd(context,value){ console.log('actions中的jiaOdd被调用了') if(context.state.sum % 2){ context.commit('JIA',value) } }, jiaWait(context,value){ console.log('actions中的jiaWait被调用了') setTimeout(()=>{ context.commit('JIA',value) },500) } }, mutations:{ JIA(state,value){ console.log('mutations中的JIA被调用了') state.sum += value }, JIAN(state,value){ console.log('mutations中的JIAN被调用了') state.sum -= value }, }, state:{ sum:0, //当前的和 school:'尚硅谷', subject:'前端', }, getters:{ bigSum(state){ return state.sum*10 } }, }
person.js:
//人员管理相关的配置 import axios from 'axios' import { nanoid } from 'nanoid' export default { namespaced:true, actions:{ addPersonWang(context,value){ if(value.name.indexOf('王') === 0){ context.commit('ADD_PERSON',value) }else{ alert('添加的人必须姓王!') } }, addPersonServer(context){ axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then( response => { context.commit('ADD_PERSON',{id:nanoid(),name:response.data}) }, error => { alert(error.message) } ) } }, mutations:{ ADD_PERSON(state,value){ console.log('mutations中的ADD_PERSON被调用了') state.personList.unshift(value) } }, state:{ personList:[ {id:'001',name:'张三'} ] }, getters:{ firstPersonName(state){ return state.personList[0].name } }, }
2.路由
路由实现了SPA(single page application)单页面应用。多页面应用切换菜单、切换页面时浏览器刷新按钮会动,页面会抖动。
npm i vue-router@3 vue2对应vue-router3,vue3对应vue-router4,否则报错。
2.1路由的基本使用
实现效果: ,/#/是vue-router默认添加的。
2.1.1创建router文件夹(和components目录、vuex的store目录、main.js、App.vue平级):
2.1.2新建index.js:
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//【引入组件
import About from '../components/About'
import Home from '../components/Home'
//创建并暴露一个路由器【new VueRouter
export default new VueRouter({
routes:[
{
path:'/about',
//【App.vue中不需要再引入这些组件和在components配置项配置这些组件了
component:About
},
{
path:'/home',
component:Home
}
]
})
2.1.3main.js:
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//【引入VueRouter
import VueRouter from 'vue-router'
//【引入路由器
import router from './router'
//关闭Vue的生产提示
Vue.config.productionTip = false
//【应用插件
Vue.use(VueRouter)
//创建vm
new Vue({
el:'#app',
render: h => h(App),
//【添加配置项,所有组件身上就都有$router、$route属性
router:router
})
2.1.4App.vue:(样式是public文件夹中的bootstrap.css中的)
bootstrap.css需要在public/index.html中引入:
<template>
<div>
<div class="row">
<div class="col-xs-offset-2 col-xs-8">
<div class="page-header"><h2>Vue Router Demo</h2></div>
</div>
</div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- 原始html中我们使用a标签实现页面的跳转 -->
<!-- <a class="list-group-item active" href="./about.html">About</a> -->
<!-- <a class="list-group-item" href="./home.html">Home</a> -->
<!-- 【Vue中借助router-link标签实现路由的切换,
active-class意为当前文字被选中时激活的样式,
用to属性来指向组件且必须带/,即index.js中配置的path属性值】 -->
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<!-- 【指定组件的呈现位置】 -->
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
//【App.vue中不需要再引入路由组件和在components配置项配置路由组件了
name:'App',
}
</script>
特别注意:router-link 标签在底层会被解析为a标签。
2.1.5Home.vue、About.vue:
<template>
<h2>我是Home的内容</h2>
</template>
<script>
export default {
name:'Home'
}
</script>
<template>
<h2>我是About的内容</h2>
</template>
<script>
export default {
name:'About'
}
</script>
2.1.6 几个注意点
路由组件就是被router文件夹下的index.js文件中引入和配置的组件。一般用views / pages文件夹存放路由组件,和components文件夹平级:
。
注册完路由,所有的路由组件和非路由组件身上都会拥有$router、$route属性。
$router一个应用仅此一个,用来管理路由规则的。
$route存储了当前页面展示的路由组件(浏览器路径/#/后的路径对应的组件)的路由配置信息,测试:
![]()
打印结果:
2.2嵌套路由/多级路由
2.2.1效果
Home菜单下嵌套News、Message菜单。
2.2.2关键代码
创建两个子路由组件News和Message,略。
router/index.js:
Home.vue路由组件中嵌套路由组件:
2.3路由的query和params参数、命名路由
2.3.1路由的query参数(Message.vue向子路由组件Detail.vue传参)
效果:
发送方Message.vue:注意其中的两种query传参方式
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- 跳转路由并携带query参数,to的字符串写法【:`${}缺一不可】 -->
<!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> -->
<!-- 跳转路由并携带query参数,to的对象写法 -->
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'Message',
data() {
return {
messageList:[
{id:'001',title:'消息001'},
{id:'002',title:'消息002'},
{id:'003',title:'消息003'}
]
}
},
}
</script>
接收方Detail.vue:接收参数的子路由组件的$route属性的query属性接收了query方式传递的参数
<template>
<ul>
<li>消息编号:{{$route.query.id}}</li>
<li>消息标题:{{$route.query.title}}</li>
</ul>
</template>
<script>
export default {
name:'Detail',
mounted() {
console.log(this.$route)
},
}
</script>
2.3.2命名路由
2.3.3路由的params参数
必须得先配置:
发送方Message.vue:
接收方Detail.vue:
<template>
<ul>
<li>消息编号:{{$route.params.id}}</li>
<li>消息标题:{{$route.params.title}}</li>
</ul>
</template>
<script>
export default {
name:'Detail',
mounted() {
// console.log(this.$route)
},
}
</script>
2.4路由的props配置
作用是减少在接收参数的路由组件中获取参数时的冗余代码,即出现过多的{{$route.params.xx}}或{{$route.query.xx}}。
2.4.1第一种写法,值为对象,值是写死的极少用
2.4.2第二种写法,值为布尔,只对params方式传参有效对query方式无效
2.4.3第三种写法,值为函数,对params、query方式传参均有效
结构赋值写法:
或者
2.5router-link标签的replace属性
2.5.1push模式,默认的
2.5.2replace模式
News.vue、Message.vue开启replace模式:
2.6编程式路由导航--$router
所谓编程式就是不通过router-link标签、通过组件的$router属性的原型对象身上的方法来编码实现路由导航的方式。
2.6.1this.$router
虽然方法在__proto__上,但是调用时还是this.$router.go()就可以。
2.6.2使用案例
back()、forward()、go():
3往前3步,-3后退3步,0刷新当前页面。
push()、replace():
$router.push()是push模式跳转路由,传参方式query/params都可以。
$router.replace()是replace模式跳转路由,传参方式query/params都可以。
2.7缓存路由组件--<keep-alive>标签
所以如果想要某路由组件被切走时(前提是父组件不销毁)不会被销毁(组件内用户已录入内容不丢失),就将其变为缓存路由组件:在其父组件中配置包裹<router-view>标签的<keep-alive>标签,并配置<keep-alive>的include属性(属性值是组件名--注意不是router/index.js中配置的代替path的name值)::
缓存路由组件上一级的父组件(Home.vue)被销毁了,这个缓存路由组件(News.vue/Message.vue)也会被销毁哦。
2.8两个新的生命周期钩子--只有缓存路由组件才有
2.8.1缓存路由组件带来的问题
2.8.2activated()、deactivated()
<template> <ul> <li :style="{opacity}">欢迎学习Vue</li> <li>news001 <input type="text"></li> <li>news002 <input type="text"></li> <li>news003 <input type="text"></li> </ul> </template> <script> export default { name:'News', data() { return { opacity:1 } }, /* beforeDestroy() { console.log('News组件即将被销毁了') clearInterval(this.timer) }, */ /* mounted(){ this.timer = setInterval(() => { console.log('@') this.opacity -= 0.01 if(this.opacity <= 0) this.opacity = 1 },16) }, */ activated() { console.log('News组件被激活了') this.timer = setInterval(() => { console.log('@') this.opacity -= 0.01 if(this.opacity <= 0) this.opacity = 1 },16) }, deactivated() { console.log('News组件失活了') //【News组件每次失活时(不是销毁)就调用】 clearInterval(this.timer) }, } </script>
注意点:
activated()、deactivated()只有缓存路由组件即keep-alive组件才有!也就是没有被<keep-alive>包裹的话,activated()、deactivated()是不起作用的。
deactivated():缓存路由组件失活时(导航栏从News缓存路由组件切到Message)调用。
activated():缓存路由组件激活时(导航栏从Message切到News缓存路由组件)调用。
缓存路由组件A初次呈现时 或者 被销毁后又呈现时--指父组件被销毁了/父导航栏被切走了,
会执行缓存路由组件A的created()/mounted()等vue钩子以及缓存路由组件A独有的activated()钩子--activated()在mounted钩子执行后执行,不会执行deactivated()钩子。
缓存路由组件A所在导航栏被切走但缓存路由组件A没有被销毁时--指父组件没有被销毁/父导航栏没有被切走--只是切到了和缓存路由组件A所在导航栏平级的其他导航栏中,此时才会调用缓存路由组件A的deactivated()钩子,,,接着再次从平级导航栏切回到缓存路由组件A所在导航栏,此时不会再执行缓存路由组件A的created()/mounted()等vue钩子和缓存路由组件A独有的deactivated()钩子,只会执行缓存路由组件A独有的activated()钩子。
简单的说activated()函数就是一个页面激活后的钩子函数,一进入页面就触发;所以当我们运用了组件缓存时,如果想每次切回来都发送一次请求的话,就需要把请求函数写在activated()中,否则写在created或mounted中其只会在首次加载该组件的时候才起作用。
deactivated()函数是一个页面失活后的钩子函数,当从缓存路由组件所在导航栏切到了与之平级的其他导航栏中时(缓存路由组件没有被销毁),绝对会调用缓存路由组件的deactivated()钩子。但当缓存路由组件的父组件所在导航栏被切走导致缓存路由组件被连带销毁时,会在此缓存路由组件销毁前调用它的deactivated()钩子吗?
待验证。
验证结果:
第一种情况
如果缓存路由组件还未失活,会在缓存路由组件销毁前调用deactivated()钩子,先执行deactivated()后执行beforeDestroy()。
第二种情况
如果缓存路由组件之前已经失活,在缓存路由组件销毁前不会再执行deactivated()了。
2.9全局路由守卫
2.9.1全局前置路由守卫--$router.beforeEach((to,from,next)=>{})
【router/index.js文件初始化的时候(刷新/#/路径时)被调用、每次路由切换之前被调用】
router/index.js路由配置文件:
注意不要使用export default new VueRouter({直接暴露,
请使用const router = new VueRouter({创建一个变量。
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'
//创建并暴露一个路由器
const router = new VueRouter({
routes:[
{
name:'guanyu',
path:'/about',
component:About,
meta:{title:'关于'}
},
{
name:'zhuye',
path:'/home',
component:Home,
meta:{title:'主页'},
children:[
{
name:'xinwen',
path:'news',
component:News,
meta:{isAuth:true,title:'新闻'}
},
{
name:'xiaoxi',
path:'message',
component:Message,
meta:{isAuth:true,title:'消息'},
children:[
{
name:'xiangqing',
path:'detail',
component:Detail,
meta:{isAuth:true,title:'详情'}
}
]
}
]
}
]
})
//全局前置路由守卫————【router/index.js文件初始化的时候(刷新/#/路径时)被调用、每次路由切换之前被调用】
router.beforeEach((to,from,next)=>{ //【to记录从哪跳转信息】【from记录跳转到哪信息】
console.log('前置路由守卫',to,from)
if(to.meta.isAuth){ //判断是否需要鉴权
if(localStorage.getItem('school')==='atguigu'){
next() //放行
} else{
alert('学校名不对,无权限查看!')
}
}else{
next() //放行路由跳转
}
})
export default router
初始化页面时,在/#/路径点击刷新时:
从Home跳转到About:
从About菜单跳转到Home菜单后,再在Home菜单下点击刷新按钮:
2.9.2全局后置路由守卫--$router.afterEach((to,from)=>{})
【router/index.js文件初始化的时候(刷新/#/路径时)被调用、每次路由切换之后被调用】
全局后置路由守卫没有next函数,后置守卫是路由跳转后执行的回调,不需要next函数。
假定router/index.js中有代码如下:
那么初始化的时候(以及刷新/#/路径时候)会打印:
初始化完成后点击About的时候会打印:
那么从About菜单跳转到Home菜单后,再在Home菜单下点击刷新按钮:
之前记录过全局前置路由守卫打印的是(to是"/home",from是"/"),
所以全局后置路由守卫打印的应该也是(to是"/home",from是"/")。
全局后置路由守卫没有next函数,后置守卫是路由跳转后执行的回调,不需要next函数。那么作用是啥?在路由跳转之后可以用它做啥?
举个栗子:可以在路由跳转成功后利用它更新网页标题为当前路由组件配置的标题::
但这样还不完美,在刚进入系统首页时,在全局后置路由守卫执行之前,在/#/界面:
其网页标题先展示的是vue-test,当全局后置路由守卫执行完才立马刷新为上图配置的‘硅谷系统’。为什么会这样?请看下图程序入口文件index.html中代码:
所以,请将index.html中的title更改为全局后置路由守卫中配置的‘硅谷系统’,
或者,将全局后置路由守卫中配置的‘硅谷系统’更改为index.html中的title。
全局前置路由守卫也可以实现上述需求,但比较麻烦,不推荐:
放行前(路由跳转被允许之后,但跳转之前)设置网页标题的代码,须配置多次
使用全局前置路由守卫完成网页标题更换时,也请将index.html中的title更改为全局前置路由守卫中配置的‘硅谷系统’,或者,将全局前置路由守卫中配置的‘硅谷系统’更改为index.html中的title。否则,也会出现很短暂的但很不好的视觉体验。
2.10独享路由守卫--beforeEnter:(to,from,next)=>{}
非全局的,某一个路由单独享用的路由守卫,只有前置,没有后置,在/router/index.js中配置。
2.11组件内路由守卫(进入组件前路由守卫/离开组件前路由守卫)--beforeRouteEnter(to,from,next)/beforeRouteLeave(to,from,next)
组件内指的是不在/router/index.js中配置,在组件内配置的路由守卫,[虽然是在组件内配置的,但是只有通过路由规则进入/跳转到该组件时、离开/跳转走该组件时,才会触发,通过写路由组件标签的方式(不通过路由规则跳转)展示的组件,不会触发这两个路由守卫!]。
和独享路由守卫区别:组件内路由守卫也是该组件独享的,但是不在/router/index.js中配置,方法名也不一样,且独享路由守卫只有一个前置方法。
和全局前置/后置路由守卫的区别:组件内路由守卫是该组件独享的,不在/router/index.js中配置,方法名也不一样,一样的是都是两个方法。除此之外:
场景1】从About菜单切到Home菜单时,会同时触发全局前置路由守卫beforeEach、全局后置路由守卫afterEach、如果Home组件配置了beforeEnter独享(只有前置)路由守卫会触发Home组件的beforeEnter,如果About组件配置了beforeRouteLeave组件内(离开组件前)路由守卫还会触发About组件的beforeRouteLeave,就算About组件配置了beforeRouteEnter组件内(进入组件前)路由守卫也不会触发About组件的beforeRouteEnter,如果Home组件配置了beforeRouteEnter组件内(进入组件前)路由守卫还会触发Home组件的beforeRouteEnter,就算Home组件配置了beforeRouteLeave组件内(离开组件前)路由守卫也不会触发Home组件的beforeRouteLeave。
场景2】从About菜单切到Home菜单后,接着在Home菜单点击浏览器刷新按钮时,会同时触发全局前置路由守卫beforeEach、全局后置路由守卫afterEach、如果Home组件配置了beforeEnter独享(只有前置)路由守卫会触发Home组件的beforeEnter,就算About组件配置了beforeRouteLeave组件内(离开组件前)路由守卫也不会触发About组件的beforeRouteLeave--因为刷新是从/#/跳到/home不经过/about,就算About组件配置了beforeRouteEnter组件内(进入组件前)路由守卫也不会触发About组件的beforeRouteEnter,如果Home组件配置了beforeRouteEnter组件内(进入组件前)路由守卫还会触发Home组件的beforeRouteEnter,就算Home组件配置了beforeRouteLeave组件内(离开组件前)路由守卫也不会触发Home组件的beforeRouteLeave。
注意:上面两个场景对应的后面的描述要成立都有一个前提::所有带next()参数的路由守卫都next()放行允许跳转路由。】
那么,执行顺序是什么呢?请看下例:
router/index.js:
About.vue:
Home.vue:
测试:
补充测试:
从About菜单切换到Home时,如果About.vue中的beforeRouteLeave不放行,则不执行路由跳转,控制台打印:
从About菜单切换到Home时,如果router/index.js中的beforeEach不放行,控制台打印:
从About菜单切换到Home时,如果router/index.js中Home路由的beforeEnter不放行,控制台打印:
从About菜单切换到Home时,如果Home.vue中的beforeRouteEnter不放行,控制台打印:
2.12history模式、hash模式
router/index.js中的路由器可以配置hash模式(默认)/history模式:
开启history模式,效果:
hash模式相比history模式兼容性略好,但不太美观。
vue的路由器默认开启的是hash工作模式——浏览器地址栏中地址含/#/,/#/和/#/以后的路径不会被Http/Https请求处理为servlet路径,不会向服务器发起请求,如下图:
vue的路由器默认开启的是hash工作模式,如果开启history模式,并且把vue项目正式部署在了服务器上,则路由路径会被浏览器处理为后端请求路径,造成404。
把vue项目部署在服务器上
打包某vue工程npm run build
不含vue文件,全是浏览器可以识别解析的文件。
dist文件夹需要部署到服务器方可正常使用,直接访问index.html的话是空白的,原因是无法展示挂载的App.vue根组件。
使用node.js + express搭建微型服务器部署dist文件夹:
1.新建demo文件夹,用vsCode打开
2.打开终端(相当于在vscode中内置的cmd面板),执行npm init,把demo文件夹变成合 法的vue工程包
3.继续执行命令 npm i express
4.新建server.js,写一个后端服务(get请求方式替换成post后也可以在apipost访问到)
5.终端执行node js文件名,启动服务
6.访问
7.新建static/public文件夹,放入vue工程打包产生的静态文件(html/css/js/图片等), 并在server.js中添加一行关键代码
8.启动,直接访问127.0.0.1:5005,默认进入index.html页面,会发现可以展示App.vue
挂载的组件了
hash模式时:
history模式时--404:
想去掉浏览器的/#/,就得把路由器设置成history工作模式,那么history工作模式下发布到服务器上造成的刷新时404怎么解决?靠后端工程师解决:有多种方式(nginx/node.js/java类库等),本质都是区分前端路由路径和后端路由路径(即servlet路径)。
介绍下node.js解决方式(如果是在node.js--js开发的后端服务器环境下):
访问npmjs.com搜索connect-history-api-fallback
如果是用node.js作为后端服务器,可以用node.js中的connect-history-api-fallback中间件(不是vue中的中间件)来解决history模式下404问题。
后端工程师在node.js后端服务器中安装connect-history-api-fallback插件:
再添加两行代码即可:
3.Element UI
Element - The world's most popular Vue UI framework
3.1基本介绍
UI组件库:
- 组件是react/vue等h5前端框架、小程序前端框架、App前端框架的概念,UI组件库有基于React的UI组件库、基于Vue的UI组件库等。
- 移动端的UI组件库,PC端的UI组件库。
3.2安装引入
3.2.1安装
npm安装(脚手架环境)
npm i element-ui -S或者npm i element-ui
或者CDN单页面引入
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
3.2.2完整引入--需要注意的是,样式文件需要单独引入
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
3.2.3按需引入--以达到减小项目体积的目的
借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。
首先,安装 babel-plugin-component(-D表示开发依赖):
npm install babel-plugin-component -D
然后,修改babel.config.js(vue-cli旧版本中是 .babelrc):
=》
module.exports = {
presets: [
'@vue/app',
["@babel/preset-env", { "modules": false }],
],
plugins: [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
接下来,如果你只希望引入部分组件,比如 Button 和 Row 和 DatePicker,那么需要在 main.js 中写入以下内容:
例如使用了Button 和 Row 和 DatePicker组件:
那么这么按需引入:
或
还可以自己定义标签名字:
一个基于 Vue 和 Element 的开发环境已经搭建完毕。
3.2.4vue3中安装element-ui
vue3中使用到是Element Plus版本,在vue3中使用Element ui的时候项目会报错。
npm install element-plus --save
修改main.js文件
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus';
import 'element-plus/theme-chalk/index.css';
createApp(App).use(ElementPlus).mount('#app');