# T[vuerouter]

vue-router

# vue3+vue-router@4版本写法

# vue路由重定向redirect

如何可以让路径默认跳到到首页, 并且<router-view>渲染首页组件呢?

{ 
	path: "/", 
	redirect: "/home" 
},
{ 
	path: "/home", 
	name: "home",
	component: () => import(/* webpackChunkName: "home-chunk" */"../pages/Home.vue"),
	meta: {
	  name: "why"
	},
	children: [
	  {
		path: "",
		redirect: "/home/message"
	  },
	  {
		path: "message",
		component: () => import("../pages/HomeMessage.vue")
	  }
	]
},

# vue 嵌套路由

  • active-class属性:设置激活a元素后应用的class,默认是router-link-active
  • exact-active-class属性:链接精准激活时,应用于渲染的 <a> 的 class,默认是router-link-exact-active;
{ 
	path: "/home", 
	name: "home",
	component: () => import(/* webpackChunkName: "home-chunk" */"../pages/Home.vue"),
	meta: {
	  name: "why"
	},
	children: [
	  {
		path: "",
		redirect: "/home/message"//如果重定向的话,要注意完整路径
	  },
	  {
		path: "message",//不需要加/开头
		component: () => import("../pages/HomeMessage.vue")
	  }
	]
},

在vue-router3.x的时候,router-link有一个tag属性,可以决定router-link到底渲染成什么元素:

  • 但是在vue-router4.x开始,该属性被移除了;
  • 而提供了更加具有灵活性的v-slot的方式来定制渲染的内容;

# vue编程式路由

// router.go(-1)
// router.forward()
// router.back()
//router.push(xxx)
router.replace(xxx)

# router-view

router-view也提供给我们一个插槽,可以用于 <transition> 和 <keep-alive> 组件来包裹你的路由组件:

  • Component:要渲染的组件;
  • route:解析出的标准化路由对象;
  <router-view v-slot="props">
      <!-- <transition name="why"> -->
        <keep-alive>
          <component :is="props.Component"></component>
        </keep-alive>
      <!-- </transition> -->
    </router-view>

# Hash 模式

www.test.com/#/ 就是 Hash URL,当 # 后面的哈希值发生变化时,可以通过 hashchange 事件来监听到 URL 的变化,从而进行跳转页面,并且无论哈希值如何变化,服务端接收到的 URL 请求永远是 www.test.com。

window.addEventListener('hashchange', () => {
  // ... 具体逻辑
})

Hash 模式相对来说更简单,并且兼容性也更好,页面不刷新,会有前进后退操作。

# History 模式

History 模式是 H5 新推出的功能,主要使用 history.pushState 和 history.replaceState 改变 URL。

通过 History 模式改变 URL 同样不会引起页面的刷新,只会更新浏览器的历史记录。

// 新增历史记录
history.pushState(stateObject, title, URL)
// 替换当前历史记录
history.replaceState(stateObject, title, URL)

当用户做出浏览器动作时,比如点击后退按钮时会触发 popState 事件

window.addEventListener('popstate', e => {
  // e.state 就是 pushState(stateObject) 中的 stateObject
  console.log(e.state)
})

# 两种模式对比

  • Hash 模式只可以更改 # 后面的内容,History 模式可以通过 API 设置任意的同源 URL
  • History 模式可以通过 API 添加任意类型的数据到历史记录中,Hash 模式只能更改哈希值,也就是字符串
  • Hash 模式无需后端配置,兼容性好。History 模式在用户手动输入地址或者刷新页面的时候会发起 URL 请求,后端需要配置 index.html 页面用于匹配不到静态资源的时候

1.安装 vue-router

cnpm i vue-router -save

2.在src下创建router目录,目录下方index.js进行路由配置 3.配置index.js

//引入vue和vue-router及所有路由组件
import Vue from 'vue'
import Router from 'vue-router'
import Login from '../components/Login'
import Index from '../components/Index'
import Work  from '../components/Work'
import Linkman from  '../components/Linkman'
import Qq from  '../components/Qq'


//使用router
Vue.use(Router)
//注意参数是routes
export default new Router({
  routes: [
    {
      path: '/',
      name: 'Login',
      component: Login
    },
    {
      path: '/qq',
      name: 'Qq',
      component:Qq
    },
    
    {
      path: '/index',
      name: 'Index',
      component: Index,
	  //有子路由的配置
      children:[
        
        {
          path: '',
          name: 'Work',
          component:Work
        },
        {
          path: 'linkman',
          name: 'Linkman',
          component:Linkman
        }      
      ]
    }
  ]
})

  1. mainjs挂载路由
import router from './router'
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})
  1. 跳转路由
import router from "../router";
router.push(`/index`)

query一般要用 path 来引入,params一般要用 name 来引入,接收参数都是类似的,分别是 this.$route.query.namethis.$route.params.name

# params模式

//params传参 使用name
this.$router.push({
  name:'second',
  params: {
    id:'20180822',
     name: 'query'
  }
})

//params接收参数
this.id = this.$route.params.id ;
this.name = this.$route.params.name ;

//路由

{
path: '/second/:id/:name',
name: 'second',
component: () => import('@/view/second')
}

# query模式

import router from "../router";
router.push(`/meetingdetail?id=${v}`)

//axiosget请求中,用的params其实和url写在?后的效果一致,其实这和vue-router无关系!!!
 let that=this;
 Indicator.open()
 this.$axios.get('/conference/selectDetailCon',{
	 params:{
		 id:that.$route.query.id
	 }
 })
//传参: 
this.$router.push({
        path:'/xxx',
        query:{
          id:id
        }
      })
  
//接收参数:
this.$route.query.id

注意

获取参数时,注意是$route!!!!

  1. $router为VueRouter实例,想要导航到不同URL,则使用$router.push方法
  2. $route为当前router跳转对象,里面可以获取name、path、query、params等

# vue动态路由匹配和name与path传参

  1. 动态路由:path中添加了动态参数
{
    path: '/about/:have',
    name: 'About11',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  • 跳转时可以使用push name的方法,也可以使用push path的方法
  • 使用path传参
this.$router.push('about/3')
// /#/about/3 浏览器可以看到3
  • 使用name传参
this.$router.push({
	name:'About11',
	params:{
		have:1000,
		hello:30000
	}
})
// /#/about/1000 浏览器只能看到hava的1000,name无法查看
// 在About.vue中可以获取$route.params => {have: 1000, hello: 30000}
// hava作为动态参数了
  • 如果不是动态路由,使用path传递params的参数,字符串方法会失败,对象方法会成功,但是params会是个空对象,使用name对象方法能成功,且所有参数都已查到
{
	path: '/testpath',
	name: 'Test',
	component: () => import(/* webpackChunkName: "test" */ '../views/Test.vue')
}
this.$router.push({
	path:'/testpath',
	params:{
		id:2
	},
	query:{
		id:1000
	}
})
//只能打印出1000

this.$router.push("/testpath/2?id=300") 
//匹配不到路由页面
this.$router.push({
	name:'Test',
	params:{
		id:2
	},
	query:{
		id:1000
	}
})
// {id: 2}
// {id: 1000}

# vue-router 404配置

vue-router@4版本,可编写一个动态路由用于匹配所有的页面

 {
    path: "/:pathMatch(.*)",//固定写法
    component: () => import("../pages/NotFound.vue")
  }
  • 通过 $route.params.pathMatch获取到传入的参数:/aa/bb/cc
  • /:pathMatch(.*)* 后面又加了一个 *,参数被解析陈数组['aa','bb','cc']

新路由 删除 *(加注星标或捕获全部)匹配方式,新增:/:pathMatch(.*) *;

 //通配符的使用
    {
      path: '/user-*',
      component:test
    },
    {
      path: '/*',
      component:notFound
    }

//{ path: '*' // 会匹配所有路径 } { path: '/user-*' // 会匹配以 `/user-` 开头的任意路径 }

# 路由的守卫

  • 全局守卫
  • 独享守卫
  • 组件内守卫

next

vue3不推荐在beforeEach使用next参数!(暂时保留)

全局的前置守卫beforeEach是在导航触发时会被回调的,它有两个参数:

  • to:即将进入的路由Route对象;
  • from:即将离开的路由Route对象;

它有返回值:

  • false:取消当前导航;
  • 不返回或者undefined:进行默认导航;
  • 返回一个路由地址:
    • 可以是一个string类型的路径;
    • 可以是一个对象,对象中包含path、query、params等信息;
    • 可选的第三个参数:next

# main.js中设置全局守卫全局守卫

作用:登录校验跳转等

回调函数中的参数,to:进入到哪个路由去,from:从哪个路由离开,next:函数,决定是否展示你要看到的路由页面。

import router from './router'

router.beforeEach((to,from,next)=>{
	console.log(to,from)
    next()
  
})
// 在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用
router.beforeResolve((to,from,next)=>{
	console.log(2)
	next()
})

router.afterEach((to,from)=>{
	console.log(to,from)
	
})

next 函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。

// BAD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  // 如果用户未能验证身份,则 `next` 会被调用两次
  next()
})
// GOOD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

# 独享守卫

用法与全局守卫一致。只是,将其写进其中一个路由对象中,只在这个路由下起作用。

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
	  path:'/give', 
	  name:"Give",
	  beforeEnter : ( to , from , next ) => {
	  		  console.log("hello")
	  },
	  component: () => import(/* webpackChunkName: "about" */ '../views/Give.vue')
	  
  }
]

const router = new VueRouter({
  routes
})

export default router

# 组件内守卫beforeRouteEnter vm

beforeRouteEnter路由守卫还未获取到this对象,只能使用next回调中的vm来获取, beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'Home',
  components: {
    HelloWorld
  },
  beforeRouteEnter:(to,from,next)=>{
        next(vm=>{
			 // 通过“vm”访问组件实例`
			//可以通过vm.name去访问data里面的name属性,跟this.name一样效果
			console.log(this)
           console.log("enter");
		   
        })
    },
  beforeRouteUpdate:(to,from,next)=>{
        
		   console.log(this)
           console.log("update");
		   next()
        
    },
beforeRouteLeave:(to,from,next)=>{
	    console.log(this)
        console.log("leave")
		next()
    },
 

  
}
</script>

# 路由组件应用实例

beforeEnter+beforeEach登录跳转控制

import { createRouter, createWebHashHistory } from 'vue-router'

const routes = [{
    path: '/',
    name: 'Home',
    component: () => import(/* webpackChunkName: "home" */ '../views/home/Home')
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import(/* webpackChunkName: "login" */ '../views/login/Login'),
    beforeEnter(to, from, next) {
      const { isLogin } = localStorage;
      isLogin ? next({ name: 'Home'}):  next();
    }
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

router.beforeEach((to, from ,next) => {
  const { isLogin } = localStorage;
  const { name } = to;
  const isLoginOrRegister = (name === "Login" || name === "Register");
  (isLogin || isLoginOrRegister) ? next() : next({ name: 'Login'});
})

export default router

  • fullpath重定向
router.beforeEach((to, from, next) => {
	if (to.meta.auth) {
		if (window.isLogin) {
			next()
		} else {
			next('/login?redirect='+to.fullPath)
		}
	} else {
			next()
	}
})
{
	path: '/about',
	meta: {
		auth: true
	}
},
{
	path: '/login',
	component: () => import('../views/Login.vue')
},

// About.vue
beforeRouteEnter(to, from, next) {
	if (window.isLogin) {
		next();
	} else {
		next("/login?redirect=" + to.fullPath);
	}
}

# 异步组件路由懒加载

集合webpack,异步组件不需要import引入在js中,好处是可以按需加载。除了首页,其他的可以触发加载。

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
 
]

const router = new VueRouter({
  routes
})

export default router

# 动态添加路由表

当有可能涉及到权限页面时,就可以使用动态路由进行配置.使用方法为addRoutes.

<template>
  <div id="app">
    <div id="nav">
		<div v-for="item in arr" >
			 <router-link :to="item.to">{{item.name}}</router-link> |
		</div>
  </div>
</template>
<script>
import Vue from "vue"

	export default{
		name:"app",
		data(){
			return{
			
				arr:[
					{to:"/",name:"Home"},
					{to:"/about",name:"About"},
				]
			}
		},
		mounted(){
			const asyncRoute = 
			  [{
				path: '/wei',
				name: 'Wei',
				component: () => import('@/views/Wei'),
				meta: {
				  title: '用户管理'
				  
				}
			  }]
			
			console.log(asyncRoute)
			
			this.$router.addRoutes([...asyncRoute])
			let tem=[];
			tem=asyncRoute.map(e=>{
				return {to:e.path,name:e.name}
			})
			console.log(tem)
			this.arr.push(...tem)
			tem=null;
			console.log(this.$router)
		}

	}
</script>
可以在登录界面/首页时请求一个从后台获取的权限数组,然后使用addRouter把路由添加进去,同时考虑到刷新页面等因素,可以在本地留下一个sessionStorage来存储这个变量,当存在这个时就不去继续获取请求,从本地添加即可。

v4版本移除addRoutes方法,可以使用addRoute逐个添加

// 动态添加路由
const categoryRoute = {
  path: "/category",
  component: () => import("../pages/Category.vue")
}

// 添加顶级路由对象
router.addRoute(categoryRoute);

// 添加二级路由对象
router.addRoute("home", {
  path: "moment",
  component: () => import("../pages/HomeMoment.vue")
})

# 动态删除路由表

# vue-router push重复点击bug

this.$router.push("/about")两次触发同样的路由会报错

NavigationDuplicated: Avoided redundant navigation to current location: "/about".

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
  return originalPush.call(this, location).catch(err => err)
}

# vue路由缓存 keep-alive

利用keepalive做组件缓存,保留组件状态,提高执行效率,同时为了避免缓存过多,还可以设置上限数量

<keep-alive include="about,home">
	<router-view></router-view>
</keep-alive>
  • 使用include或exclude时要给组件设置name
  • 两个特别的生命周期:activated、deactivated
  • 还可以动态化的控制include和exclude
//最多缓存max个数量,多了的话把前面缓存的替换掉
<keep-alive :include="['about']" max=10>
	<router-view></router-view>
</keep-alive>

# 手写vue-router思路

  1. 使用vue-router插件,router.js
import Router from 'vue-router'
Vue.use(Router)
  1. 创建Router实例,router.js
export default new Router({...})
  1. 在根组件上添加该实例,main.js
import router from './router'
new Vue({
 router,
}).$mount("#app");
  1. 添加路由视图,App.vue
<router-view></router-view>
  1. 实现一个插件
    • 实现VueRouter类
      • 处理路由选项
      • 监控url变化,hashchange
      • 响应这个变化
    • 实现install方法
      • $router注册
      • 两个全局组件router-link router-view
  • 路由表展示
import Vue from 'vue'
import VueRouter from './myrouter.js'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

const router = new VueRouter({
  routes
})

export default router
  • myrouter配置
let Vue;
class Myrouter{
	constructor(arg) {
	  console.log('2',arg)
	  this.$$options =arg
	 
	   Vue.util.defineReactive(this,'current',window.location.hash.slice(1)||"/")
	   window.addEventListener("hashchange", () => {
	     // #/about => /about
	     this.current = window.location.hash.slice(1);
		 // console.log(this.current)
	   });
	}
	push(v){
		this.current = v || '/'
	}
	
}

Myrouter.install = function (_vue){
	Vue = _vue
	console.log(1)
	// console.log(Vue)
	Vue.mixin({
		beforeCreate(){
			console.log(5)
			if(this.$options.router){
				Vue.prototype.$router = this.$options.router
			}
		}
	})
	
	Vue.component('router-link',{
		props:{
			to:{
				type:String,
				require:true
			}
		},
		render(h){
			console.log(3)
			return h('a',{
				attrs:{
					href:"#"+this.to
				}
			},this.$slots.default)
		}
	})
	
	Vue.component('router-view',{
		
		render(h){
			console.log(4)
			let component1=null
			const router =this.$router.$$options.routes.find((el)=> el.path === this.$router.current)
			if(router){
				component1 =router.component
			}
			return <component1/>
		}
	})
}

export default Myrouter

# 处理有children情况下的view-router

// 简单实现嵌套路由
// ① 记录当前路由router-view的深度
// ② 路由匹配,得到深度对应的组件

let Vue;

class Router {
  constructor(options) {
    this.$options = options;
    this.routeMap = {};
    // options.routes.forEach((route) => {
    //   this.routeMap[route.path] = route;
    // });
    // Vue.util.defineReactive(this, "current", window.location.hash.slice(1) || "/");
    this.current = window.location.hash.slice(1) || "/";
    window.addEventListener("hashchange", this.onHashchange.bind(this));
    window.addEventListener("load", this.onHashchange.bind(this));
	// 响应式数组
    Vue.util.defineReactive(this, "matched", []);
    this.match();
  }

  onHashchange() {
    this.current = window.location.hash.slice(1);
	// 页面刷新时,需要将matched数组置空,重新匹配路由
    this.matched = [];
    this.match();
  }
  // 通过 this.current 来匹配路由
  match(routes) {
    routes = routes || this.$options.routes;
    for (const route of routes) {
      if (route.path === "/" && this.current === "/") {
        this.matched.push(route);
        return;
      }
      // this.current : /about/info , route.path : /about 、 /about/info
      if (route.path !== "/" && this.current.includes(route.path)) {
        this.matched.push(route);
        if (route.children) {
          this.match(route.children);
        }
		console.log(this.matched)
        return;
      }
    }
	
    
  }

  static install(_Vue) {
    Vue = _Vue;
    Vue.mixin({
      beforeCreate() {
        if (this.$options.router) {
          Vue.prototype.$router = this.$options.router;
        }
      },
    });
    Vue.component("router-link", {
      props: {
        to: {
          type: String,
          default: "",
        },
      },
      render(h) {
        return h("a", { attrs: { href: "#" + this.to } }, this.$slots.default);
      },
    });

    Vue.component("router-view", {
      render(h) {
        // const { routeMap, current } = this.$router;
        // console.log(current);
        // const comp = (routeMap[current] && routeMap[current].component) || null;
		
	// 计算出路由的深度
        this.$vnode.data.routerView = true;
        let depth = 0;
        let parent = this.$parent;
        while (parent) {
          const vnodeData = parent.$vnode && parent.$vnode.data;
          // parent的$vnode.data.routerView存在,即该parent组件也是一个router-view,那么组件的深度就要➕1
          if (vnodeData && vnodeData.routerView) {
            depth++;
          }
          parent = parent.$parent;
        }

        const route = this.$router.matched[depth]
        const comp = route && route.component || null
        return h(comp);
      },
    });
  }
}

export default Router;

路由表中写render

 {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
	children:[{
		path:"t1",
		name:"T1",
		component:{render(){return <div>children1</div>}}
	}]
  }

总结:手写vue路由,首先要有install方法,这个是写入组件的必备方法,同时需要添加route-link和router-view两个组件,在根组件声明时,利用mixin方法在beforecreate时混入$router到vue的原型对象上,同时监听路由的变化,由于路由可以是嵌套的,要做好层级关系,对应匹配,防止内存溢出。

参考

最后更新: 6/10/2024, 9:14:30 AM