vue系列文章目录
第一章:Vue基础知识笔记(模板语法、数据绑定、事件处理、计算属性)(一)
第二章:Vue基础知识(计算属性、监视属性、computed和watch之间的区别、绑定样式)(二)
第三章:Vue基础知识(条件渲染、列表渲染、收集表单数据、过滤器)(三)
第四章:Vue基础知识(内置指令、自定义指令、Vue生命周期)(四)
第五章:Vue基础知识之组件机制(非单文件组件、单文件组件)(五)
第六章:Vue创建脚手架(六)
第七章:Vue使用脚手架(ref、props、mixin、插件、scoped)(七)
第八章:Vue组件通信(组件的自定义事件、全局事件总线、消息订阅与发布、插槽、props)(八)
第九章:Vue使用脚手架(nextTick、Vue封装的过度与动画、vue脚手架配置代理)(九)
第十章:Vuex(十)
第十一章:vue-router(基本使用、路由重定向、多级路由、路由命名、路由的query和params参数、路由的props配置)(十一)
第十三章:Vue组件通信二&数据同步(v-model、属性修饰符.sync、
a
t
t
r
s
与
attrs与
attrs与listeners、refs.
c
h
i
l
d
r
e
n
与
r
e
f
s
.
children与refs.
children与refs.parent、provide与inject )(十三)
文章目录
一、路由的两种工作模式
- 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
- hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
1. hash模式
Url格式: http://yoursite.com/#/user/id
- 默认采用的模式。地址中永远带着#号 。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
- 兼容性较好。
const router = new VueRouter({ routes: [...] })
2.history模式
Url格式: http://yoursite.com/user/id
- 地址干净,美观
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
- 这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。可以使用mode: 'history’来切换模式,默认是Hash模式
const router = new VueRouter({ mode: 'history', routes: [...] })
二、<router-link>
的replace属性
- 作用: 控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式: 分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
- 如何开启
replace
模式:<router-link replace .......>News</router-link>
注意:
当浏览器返回上一页内容时,其实返回的是上上页的内容,因为上一页内容,已经被当前页面替换掉了
二、编程式路由导航
-
作用: 不借助
<router-link>
实现路由跳转,让路由跳转更加灵活 -
具体编码:
//$router的两个API this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } }) //通过命名路由结合params进行路由传值 this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.push({ path: '/about', //通过query进行路由传值 如果参数对象中是path, params 不生效,只能通过query来传递 query: { age: 18 } }) this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退
this.$router.replace(参数)
与push非常类似,不同的是不在history中加入新的记录,而是替换当前的history记录
this.$router.go(num)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似window.history.go(n)
注意:
-
$route
和$router
是有区别的$router
为VueRouter实例,想要导航到不同URL,则使用$router.push方法$route
为当前router跳转的对象,里面可以获取name、path、query、params等 Router实例方法。
-
对比push传递params与传递query的区别
-
传递params的时候,参数不会拼接到浏览地址栏,重新刷新参数将会丢失
-
传递query的时候,参数会拼接到浏览器地址栏
-
编程式导航问题
当我们使用编程式导航来控制路由跳转时,会出现一个问题,当多次点击某个按钮时,会报错冗余导航。
原因:vue-router3.1.0之后, 引入了push()的promise的语法, 如果没有通过参数指定回调函数就返回一个promise来指定成功/失败的回调, 且内部会判断如果要跳转的路径和参数都没有变化, 会抛出一个失败的promise
解决方法有两种 :
方法一
在项目内使用Vue-Router 3.0以下版本
方法二
解决Vue-Router在3.0版本以上重复点击报错的问题,在适当的的位置对push和replace方法进行重写设置
重写一
// push
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
// replace
const originalReplace = VueRouter.prototype.replace
VueRouter.prototype.replace = function replace(location) {
return originalReplace.call(this, location).catch(err => err)
}
重写而
//需要重写VueRouter.prototype原型对象身上的push|replace方法
//先把VueRouter.prototype身上的push|replace方法进行保存一份
let originPush = VueRouter.prototype.push;
let originReplace = VueRouter.prototype.replace;
//重写VueRouter.prototype身上的push方法了
VueRouter.prototype.push = function(location, resolve, reject) {
//第一个形参:路由跳转的配置对象(query|params)
//第二个参数:undefined|箭头函数(成功的回调)
//第三个参数:undefined|箭头函数(失败的回调)
if (resolve && reject) {
//push方法传递第二个参数|第三个参数(箭头函数)
//originPush:利用call修改上下文,变为(路由组件.$router)这个对象,第二参数:配置对象、第三、第四个参数:成功和失败回调函数
originPush.call(this, location, resolve, reject);
} else {
//push方法没有产地第二个参数|第三个参数
originPush.call(
this,
location,
() => {},
() => {}
);
}
};
//重写VueRouter.prototype身上的replace方法了
VueRouter.prototype.replace = function(location, resolve, reject) {
if (resolve && reject) {
originReplace.call(this, location, resolve, reject);
} else {
originReplace.call(
this,
location,
() => {},
() => {}
);
}
};
三、缓存路由组件
keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。
- 作用: 让不展示的路由组件保持挂载,不被销毁。防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性。
- 原理: 在 created 函数调用时将需要缓存的 VNode 节点保存在 this.cache 中/在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 中取出之前缓存的 VNode 实例进行渲染。(VNode:虚拟DOM,其实就是一个JS对象
- 参数:
- 案例(代码片段):
注意: include/exclude 值是组件中的 name 命名,而不是路由中的组件 name 命名;
<!-- 缓存多个路由组件 -->
<keep-alive :include="['News','Message']">
</keep-alive>
<!-- 将缓存 name 为News的路由组件 -->
<keep-alive include="News"> //include中写的是组件的name
<router-view></router-view>
</keep-alive>
<!-- 使用正则表达式,需使用 v-bind -->
<keep-alive :include='/a|b/'>
<router-view/>
</keep-alive>
<!-- 将缓存 name 为 a 或者 b 的组件,结合动态组件使用-->
<keep-alive include='a,b'>
<router-view/>
</keep-alive>
<!-- 将不缓存 name 为 test 的组件-->
<keep-alive exclude='test'>
<router-view/>
</keep-alive>
四、两个新的生命周期钩子
-
作用: 路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
-
具体名字:
- activated路由组件被激活时触发,该钩子函数在服务器端渲染期间不被调用。
- deactivated路由组件失活时触发,该钩子函数在服务器端渲染期间不被调用。
注意: 包含在 keep-alive 中,但符合 exclude ,不会调用activated和 deactivated。
初次进入时: created > mounted > activated;退出后触发 deactivated
再次进入时: 直接触发 activated
案例(代码片段):
-
router/index.js
routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 // 子路由中也可以设置默认路径 路由重定向 { path: '/', redirect: 'news' }, { //配置子路由的时候 不需要加 / 但是在router-link中配置使用时 要写完整路径 比如/home/news path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]
-
路由页面:
Home.vue
<template> <div> <h2>Home组件内容</h2> <div> <ul class="nav nav-tabs"> <li> <router-link class="list-group-item" active-class="active" to="/home/news" >News</router-link > </li> <li> <router-link class="list-group-item" active-class="active" to="/home/message" >Message</router-link > </li> </ul> <keep-alive include="News"> <router-view></router-view> </keep-alive> </div> </div> </template>
News.vue
<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); }, created() { console.log("created"); }, mounted() { console.log("mounted"); }, activated() { console.log("News组件被激活了,activated"); this.timer = setInterval(() => { console.log("@"); this.opacity -= 0.01; if (this.opacity <= 0) this.opacity = 1; }, 1000); }, deactivated() { console.log("News组件失活了,deactivated"); clearInterval(this.timer); }, }; </script>
运行结果:
五、路由守卫
作用: 对路由进行权限控制
分类: 全局守卫、独享守卫、组件内守卫
1. 全局守卫
全局路由守卫分为全局前置路由守卫beforeEach
和全局后置路由守卫afterEach
//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'hhh'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next() //放行
}
})
//全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
console.log('afterEach',to,from)
if(to.meta.title){
document.title = to.meta.title //修改网页的title
}else{
document.title = '123'
}
})
案例(代码片段,为了节省空间,完整案例请结合上篇文章,和本篇上下文自行领会):
router/index.js
import VueRouter from 'vue-router';
import Home from '../pages/Home';
import About from '../pages/About';
import Message from '../pages/Message'
import News from '../pages/News'
const router = new VueRouter({
routes: [{
name: '关于',
path: '/about',
component: About,
meta: { title: '关于' }
},
{
name: "zhuye",
path: '/home',
component: Home,
meta: { title: '主页' },
children: [
{ path: '/', redirect: 'news' },
{
name: 'message',
path: 'message/:id/:title',
component: Message,
meta: { isAuth: true, title: '消息' }
}, {
name: 'news',
path: 'News',
component: News,
meta: { isAuth: true, title: '新闻' }
}
]
},
// {
// path: '/',
// redirect: '/home'
// }
]
});
//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to, from, next) => {
console.log('前置路由守卫,to~~', to);
console.log('前置路由守卫,from~~', from);
if (to.meta.isAuth) {
if (localStorage.getItem('school') === 'hhh') {
next()
} else {
alert('学校名不对,无权限查看!')
}
} else {
next();
}
})
//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to, from) => {
console.log('后置路由守卫,to~~~', to)
console.log('后置路由守卫,from~~~', from)
// 根据我们在每一个路由映射对象中设置的meta元数据 来进行一个动态title的显示
document.title = to.meta.title || '123'
})
export default router;
运行结果:
结果一:(localStorage中存储了school且值正确)
结果一:(localStorage中没有存储school或值不正确)
2.独享守卫
可以在路由配置上直接定义 beforeEnter 守卫。
beforeEnter(to,from,next){
console.log('beforeEnter',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next()
}
}
案例(代码片段):
import VueRouter from 'vue-router';
import Home from '../pages/Home';
import About from '../pages/About';
import Message from '../pages/Message'
import News from '../pages/News'
const router = new VueRouter({
routes: [{
name: '关于',
path: '/about',
component: About,
meta: { title: '关于' }
},
{
name: "zhuye",
path: '/home',
component: Home,
meta: { title: '主页' },
children: [
{ path: '/', redirect: 'news' },
{
name: 'message',
path: 'message/:id/:title',
component: Message,
meta: { isAuth: true, title: '消息' },
beforeEnter: (to, from, next) => {
console.log('独享路由守卫', to, from);
if (to.meta.isAuth) { //判断是否需要鉴权
if (localStorage.getItem('school') === 'hhh') {
next()
} else {
alert('学校名不对,无权限查看!')
}
} else {
next()
}
}
}, {
name: 'news',
path: 'News',
component: News,
meta: { isAuth: true, title: '新闻' }
}
]
},
]
});
export default router;
运行结果:
3. 组件内守卫
组件内守卫分为进入时路由守卫beforeRouteEnter,离开时路由守卫beforeRouteLeave,路由更新时路由守卫beforeRouteUpdate
//进入守卫:通过路由规则,进入该组件时被调用 ,不能获取组件实例 `this`
beforeRouteEnter (to, from, next) {
},
//在当前路由改变,但是该组件被复用时调用,可访问this,主要搭配动态路由一起使用监听动态路由的变化
beforeRouteUpdate (to, from, next) {
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
案例(代码片段):
About.vue
<template>
<h2>About页面</h2>
</template>
<script>
export default {
name: "About",
beforeCreate() {
console.log("beforeCreate");
},
created() {
console.log("created");
},
beforeMount() {
console.log(" beforeMount");
},
mounted() {
console.log("mounted");
},
beforeRouteEnter(to, from, next) {
console.log("About--beforeRouteEnter", to, from);
if (to.meta.isAuth) {
//判断是否需要鉴权
if (localStorage.getItem("school") === "atguigu") {
next();
} else {
alert("学校名不对,无权限查看!");
}
} else {
next();
}
},
//通过路由规则,离开该组件时被调用
beforeRouteLeave(to, from, next) {
console.log("About--beforeRouteLeave", to, from);
next();
},
beforeDestroy() {
console.log("beforeDestroy");
},
destroyed() {
console.log("destroyed");
},
};
</script>
运行结果: