路由导航过程中有若干生命周期钩子,可以在这里实现逻辑控制。
-
全局守卫,router.js
//路由配置 { path: '/about', name: "about", meta: {auth: true}, //需要认证 component: ()=>import(/*webpackChunkName: "about"*/ "./views/About.vue") } //全局守卫 router.beforeEach((to,from,next)=>{ //要访问/about 且未登录需要去登录 if(to.meta.auth && !window.isLogin){ if(window.confirm('请登录')){ //用户操作了登录 window.isLogin = true; next(); //登录成功,继续 }else{ next('/'); //放弃登录,回首页 } }else{ next(); //不需登录,继续 } })
token放在本地安全吗?有什么隐患?
-
路由独享守卫
beforeEnter(to,from,next){ //路由内部知道自己需要认证 if(!window.isLogin){ //... }else{ next(); } }
-
组件内的守卫
export default{ beforeRouteEnter(to,from,next){ // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 `this` // 因为当守卫执行前,组件实例还没被创建 }, beforeRouteUpdate(to,from,next){ // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 `this` }, beforeRouteLeave(to,from,next){ // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` } }
组件内的守卫项目应用场景:
项目中某一课程列表中的推荐课程,每次调接口都会重新返回一批不同的课程,进入课程列表看到的课程,在点击进入下级页面再回来,希望能看到刚刚进来时的数据,所以只要是页面返回当前页,需判断不重新调推荐课程接口。
export default{
name:'learnProgram',
data(){
return {
}
},
mounted(){
},
methods:{
getRecommendCourse(){
let params = {
userName:this.userName || ''
}
this.$ajax({
type:'get',
url:'/course/getCourseByRandom',
data:params
}).then((res)=>{
if(res.data.items.length != 0){
this.recomendList = res.data.,items
this.$store.commit('keeplearnPrgClass.recommendCourse',this.recomendList)
}
})
},
setData(){
//存到vuex中,返回时从vuex中取
this.$store.commit('keeplearnPrgClass.recommendCourse',[])
this.getRecommendCourse()
},
setLocalData(){
//返回时从vuex中取
this.recomendList = this.$store.state.keeplearnPrgClass.recommendCourse
}
},
beforeRouteEnter(to,from,next){
if(from.name == 'myMap' || from.name == null){
next(vm=>vm.setData())
}else{
next(vm=>vm.setLocalData)
}
},
}
Vue-router拓展
动态路由
利用$router.addRoutes()可以实现动态路由添加,常用于用户权限控制。
//router.js
//返回数据可能是这样的
//[{
// path: "/",
// name: 'home',
// component: 'Home'
//}]
//异步获取路由
api.getRoutes().then(routes => {
const routeConfig = routes.map(route => mapComponent(route));
router.addRoutes(routeConfig);
})
//映射关系
const compMap = {
'Home': () => import('./views/Home.vue')
}
//递归替换
function mapComponent(route){
route.component = compMap[route.component];
if(route.children){
route.children = route.children.map(child => mapComponent(child))
}
return route
}
面包屑
利用$route.matched可得到路由匹配数组 ,按顺序解析可得路由层次关系。
//Breadcrumb.vue
watch: {
$route(){
//[{name:'home'},{name:'list'}]
console.log(this.$route.matched);
//['home','list']
this.crumbData = this.$toute.matched.map(m => m.name)
}
}
Vue-router 源码实现
通常用法
//krouter.js
import Home from './views/Home';
import About from './views/About';
Vue.use(VueRouter);
export default new VueRouter({
routes: [
{path: '/',component: Home},
{path: '/about', component: Abbout}
]
})
//main.js
import router from './krouter'
分析一下需要完成的任务:
- 要能解析routes配置,变成一个key为path,value为component的map
- 要能监控url变化事件,把最新的hash值不保存到current路由
- 要定义两个全局组件:router-view用于显示匹配组件内容,router-link用于修改hash
- current应该是响应式的,这样可以触发router-view的重新渲染
具体实现
创建krouter.js
let Vue;
class VueRouter{
constructor(options){
this.$options = options;
//创建一个路由path和route映射
this.routeMap = {};
//将来当前路径current需要响应式
//利用Vue响应式原理可以做到这一点
this.map = new Vue({
data: {
current: '/'
}
})
}
init(){
//绑定浏览器事件
this.bindEvents();
//解析路由配置
this.createRouteMap(this.$options)
//创建router-link和
this.initComponent()
}
bindEvents(){
window.addEventListener('hashchange',this.onHashchange.bind(this))
window.addEventListener('load',this.onHashchange.bind(this))
}
onHashchange(e){
// http://localhost/#/home
this.app.current = window.location.hash.slice(1) || '/'
}
createRouteMap(options){
options.routes.forEach(item => {
// ['/home']: {path: '/home',component: Home}
this.routeMap[item.path] = item;
})
}
initComponent(){
Vue.component('router-link',{
props: {
to: String
},
render(h){
//目标是:<a :href="to">
return h('a', {attrs:{href:'#'+this.to}}, this.$slots.default)
//jsx 写法
// return <a href={this.to}>{this.$slots.default}</a>
}
})
//hash -> current -> render
Vue.component('router-view',{
//箭头函数能保留this指向,这里指向VueRouter实例
render: (h) => {
const Comp = this.routeMap[this.app.current].component;
return h(Comp);
}
})
}
}
//把VueRouter 变为插件
VueRouter.install = function(_Vue){
Vue = _Vue; //这里保存,上面使用
//混入任务
Vue.mixin({ //混入:就是扩展Vue
beforeCreate(){
//这里的代码将来会在外面初始化的时候被调用
//这样我们就实现了Vue扩展
// this 是谁? Vue的组件实例
//但是这里只希望跟组件执行一次,只有跟组件有 $options.router
if(this.$options.router){
Vue.prototype.$touter = this.$options.router;
this.$options.router.init();
}
}
})
}