Skip to content

路由Router

1. 相关理解

1.1 vue-router 的理解

vue的一个插件库,专门用来实现SPA单页面应用

1.2 对SPA应用的理解

  1. 单页Web应用(single page web application,SPA)

  2. 整个应用只有一个完整的页面

  3. 点击页面中的导航链接不会刷新页面只会做页面的局部更新

  4. 数据需要通过ajax请求获取

1.3 路由的理解

  1. 什么是路由?

    1. 一个路由就是一组映射关系(key - value)

    2. key路径value可能是functioncomponen

  2. 路由分类

    1. 后端路由

      1. 理解:valuefunction,用于处理客户端提交的请求

      2. 工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据

    2. 前端路由

      1. 理解:valuecomponent,用于展示页面内容

      2. 工作过程:当浏览器的路径改变时,对应的组件就会显示

2. 基本路由

使用步骤

1.安装vue-router

  • 命令行下载npm i vue-router@3
  • 注意:vue2需要下载vue-router@3版本,vue3需要下载vue-router@4版本

2.配置main.js

(1)引入VueRouterrouter, router是下面需要创建的路由配置文件

main.js

js
import VueRouter from 'vue-router'
import router from './router' //如果文件夹下只有一个index.js文件,路径写到文件夹就行

(2)使用VueRouter

Vue.use(VueRouter)

(3)在new Vue({})中添加router:router配置项,简写router

src/main.js

js
import Vue from 'vue'
import App from './App.vue'

import VueRouter from 'vue-router'
import router from './router'

Vue.config.productionTip = false

Vue.use(router)

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

3.创建路由组件

  1. 创建pages文件夹,路由组件在此文件夹内创建

  2. 路由组件和普通组件一样,单独放在一个文件夹里是为了分类

4.配置router

(1)在src下创建router文件夹并创建index.js文件

(2)引入vue路由VueRouter

js
import VueRouter from 'vue-router'

(3)引入路由组件

js
import About from '../pages/About'
import Home from '../pages/Home'

(4)创建并暴露一个路由器

  1. VueRouter里有一个对象参数,路由以数组的格式存放在routes属性,每个单独路由是一个对象

    对象有基本的path,component属性

    • path是路径

    • component是组件名

  2. 由于网页打开默认路由路径是"/",没有实际内容,可以在routes数组中添加一个对象,用redirect:'/home' 属性强制修改url上的路径

src/router/index.js

js
import VueRouter from 'vue-router';
import About from '../pages/About'
import Home from '../pages/Home'

export default new VueRouter({
    routes:[
        {
            path:'/',       // 网页打开默认路由路径是"/"
            redirect:'/home'      //强制修改url上的路径为  /home
        },
        {
            path:'/about',    //路径前要加    /
            component:About
        },
        {
            path:'/home',
            component:Home
        }
    ]
})

5.实现切换

  1. 组件内需要添加路由的地方添加<router-link></router-link>标签,浏览器会被替换为a标签
    • to属性用来设置需要跳转的路由组件路径

    • active-class属性可配置选中类名,样式需要在css中设置

html
.active{
	color: red;
}
...
<router-link active-class="active" to="/about">About</router-link>

6.指定展示位

  1. 在组件内需要显示路由的地方添加<router-view></router-view>

3. 几个注意事项

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹

  2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载

  3. 每个组件都有自己的**$route属性,里面存储着自己的路由信息**

  4. 整个应用只有一个router,可以通过组件的**$router**属性获取到

  5. 重定向和别名

    1. redirect:"路径" 重定向/跳转
    2. alias:''路径别名"

浏览器地址改变后,会在routes中寻找路径对应的路由组件

  • 第一个path匹配后,就不会继续走下一个
  • 如果找不到,就会404
js
const router = new VueRouter({
 routes: [
     {path:"路径" , name:"", component:组件名, }
     {path:"路径" , name:"", component:组件名, }
     {path:"*" , redirect:"路径", alias:"路径别名"}
 ]
})

path支持通配符*

  * 	  通配,表示匹配所有path
  my-*    表示my-开头的就匹配

如此,就可以在最后增加一个路由配置,path"*",再用redirect:'/404'重定向,当网址错误时自动跳转404页面

js
{ path:"*" , redirect:"/404", component:Err404 }

4. 嵌套路由和命名视图

1.嵌套路由

  1. 配置路由规则,在需要嵌套的组件路径中,使用children属性,children是个数组,子路由组件是对象

步骤

1.创建二级路由组件

  • 为了方便整理,可以在pages里再创建一个文件夹,里边存放二级路由组件

2.配置router.js

(1)同样需要先引入路由组件

js
import Child1 from '../pages/ChildRoute/Child1'
import Child2 from '../pages/ChildRoute/Child2'

(2)在一级路由组件的对象内添加children属性,children是个数组子路由组件是对象

  • children里同样有path,和component属性,注意子路由组件的path值中不能加/

router/index.js

js
import VueRouter from 'vue-router'
// 引入组件
import Home from '../pages/Home'
import About from '../pages/About'
import Child1 from '../pages/ChildRoute/Child1'
import Child2 from '../pages/ChildRoute/Child2'

// 创建并暴露一个路由器
export default new VueRouter({
    routes: [
        {
            path:'/',
            redirect:'/home'
        },
        {
            path: '/home',
            component: Home,
            children:[
                {
                    path:'child1',      // 此处一定不要带斜杠   /
                    component:Child1    
                },
                {
                    path:'child2',
                    component:Child2
                }
            ]
        },
        {
            path: '/about',
            component: About
        }
    ]
})

3.跳转(要写完整路径)

html
<router-link to="/home/child1">News</router-link>

4.指定展示位

  1. 组件内需要显示路由的地方添加<router-view></router-view>

如果要继续嵌套更深层的路由,继续用children无限套娃,工作中顶多套三层

2.命名视图

一个路由,可以显示多个组件,并且组件为兄弟关系

html
<router-view></router-view>
<router-view name="a"></router-view>
<router-view name="b"></router-view>
js
const foo={ template:`<h1>主页面</h1>` }
const navbar={ template:`<h1>导航</h1>` }
const footer={ template:`<h1>页脚</h1>` }

const router = new Router({
    routes:[
        path:'/',
        components:{
        	default:'foo',
        	a:navbar,
        	b:footer
        }
    ]
})

5. 命名路由

1.作用:可以简化路由的跳转

2.如何使用

(1)给路由命名

在路由配置中添加name属性

js
{
    path:'/demo',
    component:Demo,
    children:[
        {
            path:'test',
            component:Test,
            children:[
                {
                    name:'hello' // 给路由命名
                    path:'welcome',
                    component:Hello,
                }
	    	]
	}
    ]
}

(2)简化跳转,不用写很长的路径名

注意:想要在to里直接写路由的name,必须给to动态绑定,且内容写成对象的方式

html
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>

<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>

<!--简化写法配合传递参数 -->
<router-link 
	:to="{
	    name:'hello',
	    query:{
                id:666,
                title:'你好'
		}
	 }"
>跳转</router-link>

6. 路由 query 传参

1.传递参数

  • 跳转并携带query参数,to的字符串写法
html
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">跳转</router-link>
  • 跳转并携带query参数,to的对象写法(推荐)
html
<router-link 
    :to="{
    path:'/home/message/detail',
    query:{
        id: 1,
        title: '标题'
    }
}"
>跳转</router-link>

2.接收参数

js
//视图中
$route.query.id
$route.query.title

//方法中
this.$route.query.键

7. 路由 params 传参

1.接收params参数

需要在路由配置中,在path路径后用:占位符接收params参数,不同的参数用/分隔

js
{
    path:'/home',
    component:Home,
    children:[
        {
            path:'news',
            component:News
        },
        {
            component:Msg,
            children:[
                {
                    name:'detail',
                    path:'detail/:id/:title', // 🔴使用:占位符声明接收params参数
                    component:Detail
                }
            ]
        }
    ]
}

2.传递params参数

特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

  • 跳转并携带params参数,to的字符串写法
html
<router-link to="/home/message/detail/666/你好">跳转</router-link>
  • 跳转并携带params参数,to的对象写法
html
<router-link 
    :to="{
        name:'detail',
        params:{
            id:666,
            title:'你好'
        }
    }"
>跳转</router-link>

3.接收参数

js
$route.params.id
$route.params.title

params和query的区别

  1. query可以通过name属性或者path属性来引入路由,而params只能通过name属性来引入路由;在使用params传递时如果指定了path属性而没有name属性,那么界面能成功跳转但是不能接受到传递过来的参数

  2. 在刷新界面时,query传递的参数不会丢失,而params会丢失

  3. 直白的来说query相当于get请求,页面跳转的时候,可以在地址栏看到请求参数,而params相当于post请求,参数不会再地址栏中显示。

8. 路由的 props 配置

props作用:让路由组件更方便的收到参数

js
{
	name:'xiangqing',
	path:'detail/:id',
	component:Detail,

	//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
	// props:{a:900}

	//第二种写法:props值为布尔值,为true时,则把路由收到的所有params参数通过props传给Detail组件
	// props:true
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
         //基础写法为传入实参$route,也可以使用解构赋值
	props($route){
		return {
			id: $route.query.id,
			title: $route.query.title
		}
	}
}

src/router/index.js

js
import VueRouter from "vue-router";
import Home from '../pages/Home'
import About from '../pages/About'
import News from '../pages/News'
import Message from '../pages/Message'
import Detail from '../pages/Detail'

export default new VueRouter({
  routes:[
    {
      path: '/about',
      component: About
    },
    {
      path:'/home',
      component:Home,
      children:[
        {
          path:'news',
          component:News
        },
        {
          path:'message',
          component:Message,
          children:[
            {
              name:'xiangqing',
              path:'detail/:id/:title',
              component:Detail,
              // props的第一种写法,值为对象,
              // 该对象中的所有key-value都会以props的形式传给Detail组件
              // props:{a:1,b:'hello'}

              // props的第二种写法,值为布尔值,
              // 若布尔值为真,会把该路由组件收到的所有params参数,以props的形式传给Detail组件
              // props:true

              // props的第三种写法,值为函数
              props(params) { // 这里可以使用解构赋值
                return {
                  id: params.id,
                  title: params.title,
                }
              }
            }
          ]
        }
      ]
    }
  ]
})

src/pages/Message.vue

vue
<template>
	<div>
    <ul>
      <li v-for="m in messageList" :key="m.id">
        <router-link :to="{
                name:'xiangqing',
                params:{
                    id:m.id,
                    title:m.title
                }
         }">
          	{{m.title}}
  			</router-link>&nbsp;&nbsp;
  		</li>
  	</ul>
    <hr/>
    <router-view></router-view>
  </div>
</template>

<script>
    export default {
        name:'News',
        data(){
            return{
                messageList:[
                    {id:'001',title:'消息001'},
                    {id:'002',title:'消息002'},
                    {id:'003',title:'消息003'}
                ]
            }
        }
    }
</script>

src/pages/Detail.vue

vue
<template>
    <ul>
        <li>消息编号:{{ id }}</li>
        <li>消息标题:{{ title }}</li>
    </ul>
</template>

<script>
    export default {
        name:'Detail',
        props:['id','title']
    }
</script>

9. 路由跳转的 replace 方法

  1. 作用:控制路由跳转时操作浏览器历史记录的模式,就是js跳转路由
  2. 浏览器的历史记录有两种写入方式:pushreplace
    1. push追加历史记录,路由跳转时候默认为push方式

    2. replace替换当前记录

  3. 开启replace模式
    1. <router-link :replace="true" ...>News</router-link>

    2. 简写<router-link replace ...>News</router-link>

总结:浏览记录本质是一个默认push,点开新页面就会在栈顶追加一个地址,后退,栈顶指针向下移动,改为replace就是不追加,而将栈顶地址替换

src/pages/Home.vue

vue
<template>
  <div>
    <h2>Home组件内容</h2>
    <div>
      <ul class="nav nav-tabs">
        <li>
          <router-link replace class="list-group-item" active-class="active" 
                       to="/home/news">News</router-link>
    	</li>
        <li>
          <router-link replace class="list-group-item" active-class="active" 
                       to="/home/message">Message</router-link>
    	</li>
      </ul>
    <router-view></router-view>
    </div>
  </div>
</template>

<script>
  export default {
    name:'Home'
  }
</script>

10. 编程式路由导航

作用:实质上是js跳转路由,让路由跳转更加灵活

this.$router.push({}) 内传的对象与<router-link>中的to相同

js
this.$router.push({
    path:'/路径', 
    query: {参数名:值} 
})

this.$router.push({ 
    name:'名称',
    params: {参数名:值} 
})

获取:this.$route.query/params.参数名

this.$router.replace({ })

路由的replace跳转是替换浏览器当前地址记录,也就是替换栈顶

this.$router.resolve({ })

this.$router.forward() 前进,相当于浏览器前进

this.$router.back() 后退,相当于浏览器后退

this.$router.go(n) 可前进也可后退,n为正数前进n,为负数后退

11. 缓存路由组件

作用:让不展示的路由组件保持挂载,不被销毁

用法

  1. 在需要缓存的组件的父组件里的<router-view></router-view>外面包含上<keep-alive></keep-alive>
  2. include属性可选择需要缓存的组件

keep-alive可以设置以下props属性:

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
  • max - 数字。最多可以缓存多少组件实例
vue
// 缓存一个路由组件
<keep-alive include="News">
    <router-view></router-view>
</keep-alive>
// 缓存多个路由组件,注意动态绑定
<keep-alive :include="['News', 'Message']">
    <router-view></router-view>
</keep-alive>

若要缓存所有组件,则不需要加include

12. 两个新生命周期钩子

  1. activated 路由组件被激活时触发

  2. deactivated 路由组件失活时触发

js
//...
activated(){
	console.log('组件激活')
},
deactivated(){
	console.log('组件失活')
}
//...

13. 路由守卫

作用对路由进行权限控制

分类全局守卫独享守卫组件内守卫

1.全局守卫

书写在VueRouter({})内,和routes:[]同级

1.1全局前置守卫

进入下一个组件之触发

js
router.beforeEach((to, from, next) => {
  // ...
})

参数

  1. to: 要进入的目标路由对象(去哪)

  2. from: 要离开的路由对象(从哪来)

  3. next: 是否要进行下一步(要不要继续)

    • next()相当于 next(true) 继续执行

    • 不写 相当于next(false)终止执行

    • next(path)跳转

js
next('/login')
// 或者
next({path:'/login'})

登录鉴权

permission.js

js
import router from '@/router'
import store from '@/store'

router.beforeEach((to,from,next)=>{
    const whiteList=['/login','/login/sms','/login/token','/404']
    if(whiteList.includes(to.path)){
        next()
    }else{
        // store中获取token
        const token=store.state.users.token
        if(token){
            if(!store.state.auths.menu.length){
                // 获取菜单列表
                store.dispatch('auths/FETCH_MENU')
            }
            next()
        }else{
            next({path:'/login'})
        }
    }
})

在main.js中导入

main.js

js
import "@/permission.js"

1.2全局后置守卫

进入下一个组件之后触发,可以用来改变页面的title

js
router.afterEach((to, from) => {
  // ...
})

tofrom属性和前置守卫一样

注意:后置钩子afterEach没有next参数

1.3 meta

meta是路由route里的一个对象

它可以切换不同页面显示不同的页面标题文字,也可以判断守卫放行的页面

给meta设置isAuth属性可以用来判断是否放行

通过this.$route获取

1.4实例

要使用理由守卫,就要讲路由先创建配置再暴露,不能直接暴露

js
const router= new VueRouter({
    routes: [
        {
            name:'guanyu',
            path: '/about',
            component: About,
            meta:{title:'关于'}
            
        },
        {
            path: '/home',
            name:'zhuye',
            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:{title:'详情'},
                            props({query}){
                                return {
                                    id:query.id,
                                    title:query.title
                                }
                            }
                        }
                    ]
                }
            ]
        }
    ]
})
//全局前置路由守卫-----初始化的时候被调用,每次路由切换被调用
router.beforeEach((to,from,next)=>{
    console.log('前置路由守卫',to,from);
    
   if(to.meta.isAuth){   //判断是否需要鉴权
        if(localStorage.getItem('school')==='atguigu2'){
            next()
        }else{
            alert('您无权通行')
        }
   }else{
    next()
   }
})



router.afterEach((to,from)=>{
    document.title=to.meta.title||'你好'
})

export default router

2. 独享路由守卫

beforeEnter

书写在单独的路由配置里

js
{
	name:'news',
	path:'news',
	component:News,
	beforeEnter:(to,from,next)=>{
		console.log('路由独享')
	}
}

3. 组件内路由守卫

顾名思义,组件内路由守卫需要在组件内书写,并非在router.js文件书写

js
export default {
	name:'About',
	...
	//通过路由规则,进入该组件时被调用
	beforeRouteEnter(to,from,next){
		if(to.meta.isAuth){ //判断是否需要鉴权
        	next() //放行
   		 }
	},
    //在当前路由改变,但是该组件被复用时调用
    beforeRouteUpdate(to,from,next){
		...
	},
	//通过路由规则,离开该组件时被调用
	beforeRouteLeave(to,from,next){
		next() //放行
	}
}

14. 路由模式

路由模式有三种,分别为hashhistoryabstract

通过router里的mode属性切换路由模式

js
const router=new VueRouter({
    mode:'history',
    routes:[...]
})

1.hash路由

  1. 浏览器url中**#及其后面的内容就是hash值**,可通过location.hash获取
  2. hash值不会包含在HTTP请求中,即:hash值不会带给服务器。路由默认是hash模式

1.优缺点:

  1. 地址中永远带着#号,不美观。
  2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
  3. 兼容性较好。

2.原理

hash路由是由Bom Api中的location.hash改变浏览器地址,通过window.onhashchange事件监听地址栏变化,从而切换不同的组件

通过location.hash切换url时,页面并不会发生任何变化,也不会刷新

html
<body>
    <button class="btn">切换</button>
    <script>
        document.querySelector(".btn").addEventListener("click", () => {
            location.hash = "test";
            console.log(location.hash)
        });

        window.onhashchange = function (e) {
            console.log(e.newURL);
        };
    </script>
</body>

2.history路由

1.优缺点

  1. 地址干净,美观。
  2. 兼容性和hash模式相比略差,刷新会丢失页面
  3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。

2.原理

语法

js
history.pushState(stateObject, title, url);

参数

参数描述
stateObject传入的状态对象。当前进(后退)到某一新的状态时,会触发popstate事件。此事件对象event.state存储的就是这个stateObject的值。
title新状态的标题。(目前,大多数浏览器并不支持该参数,建议传null值)
url状态对应的历史记录的地址。
html
<body>
    <button class="btn">切换</button>
    <script>
        document.querySelector(".btn").addEventListener("click", () => {
            history.pushState(null, null, "/test");
        });

        window.onpopstate = function (event) {
            console.log(event);
        };
    </script>
</body>

MIT Licensed