简述

vue-router路由管理也是vue中特别重要的一篇,下面来看看在本篇中主要学习哪些内容

学习路由管理的前置知识

本节中主要讲网站架构发展过程,以及前后端路由,前后端渲染的概念
最后介绍当更改浏览器URL却不向服务器再申请资源的方法,下面来看具体详情:

  • 网站架构发展过程

    • 前后端不分离阶段
      以前学习JSP的时候,或者使用thymeleaf的时候,就是前后端不分离的时候,没有用ajax
      直接拿到数据在页面中嵌入java代码等,并且也是后端根据用户的请求,将页面响应给用户。
    • 前后端分离阶段
      再之后,就是学习了ajax,通过Jquery发送ajax请求,从后端获取到数据,再通过JS遍历数据展示
      在页面中,但是注意,这个时候依然是后端根据用户请求,选择将某个页面发送给用户。
    • 单页面富应用阶段
      到现在,整个项目只有一个页面了,其他页面都是一个个前端人员编写的组件通过Vue渲染出来的
      富应用说的就是多个组件,在这个阶段中,用户只在最开始进入网站的时候发送请求给后端,接收到
      后端数据之后,便把所有静态数据全部渲染了,当用户点击页面某个按钮,其实是跳转到了相应的组件中
      这个阶段在前后端分离阶段的基础之上增加了一点,就是页面的跳转由前端主导,而不是后端。
  • 前后端路由和前后端渲染的概念
    通过上面的解释,我们很容易知道,后端路由指的是,后端处理URL和页面之间的映射关系,即跳转页面后端来做
    前端路由则是跳转页面由前端进行处理,这不,vue-router就是干这事的。
    后端渲染指的是,数据在后端就已经渲染到页面中,直接给用户发送HTML页面就完事
    前端渲染则是,数据由前端渲染,你后端只是提供数据而已,好处是,以后IOS端,桌面软件都可以共用一个后端即可。

  • 更改浏览器URL却不向服务器再申请资源的方法
    上面说过,在单页面富应用阶段,只有在用户进入网站的时候才会向服务器申请唯一一套资源,之后用户每次
    点击按钮,会发生url改变,但是并不向后端请求数据,而是通过切换组件达到跳转页面的效果
    这种改变浏览器url,却不向服务器发出请求是怎么做的呢?下面是两种方式:

    /*	
    	1,通过改变浏览器的hash值,使得改变浏览器url,但不向服务器发出请求
    	使用这种方式改变url,被称为hash模式,hash模式也是vue路由中默认使用的模式,hash模式有个特点,就是url路径中会带有#
    */
    
    /*
    	2,使用html5的history对象使得改变url,但不请求服务器,使用history对象方式被称为history模式
    	history的pushState方法,相当于入栈操作,使用这个方法,会将这个url对应的组件放进一个栈结构中,
    		页面始终显示栈顶url对应的组件页面
    	history的back方法,相当于出栈操作,就是浏览器返回上一个页面,不过在vue中称为返回上一个组件
    	history的replaceState方法,这个方法相当于替换了栈顶的url,这个意思是,浏览器不能返回上一个页面
    	history的go方法,history.back()等价于history.go(-1),history.forward()等价于history.go(1),指定跳转到某个页面
    */
    
    /*
    	2021年10月3日更新:
    		hash模式和history模式,区别不仅仅是浏览器路径中的#,当项目被打包部署到服务器上时
    		如果刷新页面,hash模式的#后面的路径不会发送到后端服务器中,不会产生问题,
    		而如果是history模式,则一旦页面刷新,浏览器错把路由跳转的路径当成后端服务器接口请求,
    		结果服务器没这接口,直接404,项目上线时要想使用history模式,需要后端的配合,进行相应的处理
    		或者配置nginx
    */

vue-router的基本使用

目前三大框架都有自己的路由实现,vue就是vue-router,路由用于设定访问路径,将路径和组件映射起来
在vue-router的单页面应用中,页面路径的改变就是组件的切换,下面是vue路由使用步骤:

  • 安装vue-router

    npm install vue-router --save	//运行时依赖
  • 如果没有自动在src目录下创建router目录的话,我们需要在src下创建一个router目录并在其中创建index.js文件
    该JS文件是vue-router的配置文件,我们需要在其中导入vue和vue-router,并且将vue-router注册进Vue中
    准确来说,vue-router是vue的插件,只要是插件都需要注册到vue中,插件主要是用来为vue添加全局功能的
    下面是router/index.js初始配置:

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    Vue.use(VueRouter)		//注册插件
    const routes = [		//具体的映射关系,配置url对应某些组件的
    
    ]
    const router = new VueRouter({	//创建router实例,并导出该实例
        mode: 'history',		//使用history模式更改url,路径中就不会有#
        base: process.env.BASE_URL,
        routes		//将映射关系装载到router中,es6属性的增强写法,等同于routers:routers
    })
    export default router
  • 最后在main.js中,将router装载到vue实例中,下面是main.js中内容

    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'	//当不写后面具体文件时,默认找该路径下的index.js文件
    
    new Vue({
        router,		//装载路由对象
        render: h => h(App)	//组件篇讲过,代替template+components,就是生成一个<App/>标签
    }).$mount('#app')	//绑定页面中id为app的div标签

总结下来,不过是安装插件,注册插件,在路由配置文件index.js中配置路由映射信息,再在main.js中导入router
上面这些是基本配置,下面我们创建一个HelloVueRouter组件,并为其配置路由映射关系

  • 首先在src/components目录下创建一个HelloVueRouter.vue文件,具体内容如下:

    //组件中就显示Hello VueRouter
    <template>
      Hello VueRouter
    </template>
    
    <script>
    export default {	//导出组件对象
    name: "HelloVueRouter"
    }
    </script>
    
    <style scoped>
    </style>
  • 在router/index.js中配置路由信息

    //只需要在这里添加映射信息,其余不变
    import helloVueRouter from '../components/HelloVueRouter.vue'
    const routes = [		
    	{
            path: '/helloVueRouter',
            name : 'helloVueRouter',
            component: helloVueRouter
      	}
    ]
  • 在App.vue中的template中使用vue-router的组件标签,router-link标签和router-view标签,这两个标签
    都是vue-router中的组件,其中router-link标签用于跳转组件,router-view标签用于将组件展示到哪里
    下面是app.vue中具体详情:

    <template>
      <div>
        <router-link to="/helloVueRouter">helloVueRouter</router-link>
      </div>
      <router-view/>
    </template>
    
    <script>
    export default {
      name: 'App'
    }
    </script>
    <style>
    </style>
    /*
    	结合上面的部分我们知道,当我们打开浏览器时,app.vue中的template会替换页面原来的id为app的div标签
    	在app.vue的template中,使用router-link标签的to属性,去找到到该路径下对应的组件,也就是helloVueRouter组件
    	这在router/index.js路由配置文件中配置好的,找到该组件后,将组件加载到router-view所在的地方
    	从而可以在页面中显示helloVueRouter组件的template中的内容
    */

vue-router的详解

这节主要讲一些路由的细节信息,包括默认路由,router-link标签的补充,通过代码跳转路由,动态路由,路由的懒加载

  • 配置默认路由
    默认路由指的是,最先加载哪个组件,当app.vue的template中有多个router-link标签时
    但是因为router-view标签表示一次只能显示一个组件,所以默认路由就是配置哪个组件最先展示。

    //通过在router/index.js的routes中配置默认路由
    const routes = [
        {
            path: '/',
            redirect: '/helloVueRouter'	//重定向
        },
    	{
            path: '/helloVueRouter',
            name : 'helloVueRouter',
            component: helloVueRouter
      	}
    ]
    /*
    	一般默认路由都是配置path为/,这样表示,页面一打开,最先展示哪个组件,上面的例子使用重定向方式
    	你也可以直接将某个组件的path改成/,表示这是最先加载
    		{
                path: '/',
                name : 'helloVueRouter',
                component: helloVueRouter
            }
        小提示:app.vue组件并不需要配置路由映射,因为该组件的作用就是使用组件内的template代替页面的div标签
    */
  • router-link标签的补充
    该标签是一个vue-router中已经内置的组件,它会被渲染成一个a标签,下面是该标签的一些属性:

    • to属性:指定跳转的url路径,该路径对应路由表中某个组件

    • tag属性:tag可以指定<router-link>之后渲染成什么标签,比如渲染成li标签,而不是a标签

    • replace属性:该属性不需要任何值,仅仅只是标记这个组件,指定replace的情况下,
      浏览器后退键按钮不能返回到上一个页面中,底层应该用的是history对象的replaceState方法更改浏览器url的

    • active-class属性,这个属性的作用是,当组件被展示的时候代表这个组件被激活了,我们的router-link标签
      本质上是一个a标签,点击a标签后,组件被激活,此时a标签的class属性值中会有一个router-link-active样式
      我们可以通过该样式,对这个a标签做一些处理,比如菜单栏,你点击某一栏,对应的组件被展示出来,
      而你点击后的这一栏会更改颜色,就是使用router-link-active样式做的。
      而我们的active-class属性则可以设置成其他样式名,改掉router-link-active为其他样式名,
      改这个样式名有几种方式,下面总结一下:

      //1,通过router-link标签的active-class属性更改router-link-active样式名
      	<router-link to="/wanyi" active-class="wanyi">科一</router-link>
      //2,通过在路由文件中增加linkActiveClass属性,使用该属性,当组件被点击后,会增加对应的样式名
      	const router = createRouter({
              history: createWebHashHistory(),	//设置history模式,有很多方式
              routes,		//路由表
              linkActiveClass: 'wanyi'	//将router-link-active样式名改成wanyi
          })
  • 通过代码跳转路由
    上面例子都是通过to属性,使得我们的url跳转其他地址,从而替换组件,我们也可以通过代码实现同样的效果
    下面让我们来看看如何通过代码方式改变url路径吧

    <router-link to="/wanyi" active-class="yiwan">科一</router-link> //原来的
    
    <button @click="toWanyi()">科一</button>
    <script>
        export default {
            name: 'App',
            methods: {
                toWanyi(){
                    this.$router.push('/wanyi')
                }
            }
    	}
    </script>
    /*
    	简单来说就是使用button+点击事件方式替换了router-link,通过$router.push方法跳转路径
    	push方法就相当于history对象的pushState方法,浏览器是可以回退到上一个url的,
    	如果是replace方法,浏览器就不可回退了。这个$router就是我们在router/index.js中创建的路由实例router
    	这里之所以能使用$router是因为在注册进vue实例时,使用Vue.prototype.$router=router
    	又因为我们组件都是继承自Vue的原型对象,是Vue构造函数的实例,所以我们组件中可以获取到$router
    	另外之后你还会见到$route,它指的是当前路由,也就是路由映射关系中那个对象,之后会详细说明
    */
  • 动态路由
    动态路由指的是,我的url路径某一段是不确定的,类似后端的路径变量,我们可以获取路径上这个变量的值
    这也算是一种路由传递数据的方式,数据从一个组件,传递到另一个组件。下面是一个简单的例子:

    原来的:
        //路由配置文件中,router/index.js
        {
            path: '/wanyi',
            name: 'Wanyi',
            component: Wanyi
        }
        //App.vue
        <router-link to="/wanyi">科一</router-link>
        //当我点击跳转时,路径变成/wanyi,会匹配到对应的path,找到Wanyi组件进行渲染
    
    //我想实现,点击跳转时,/wanyi/[id],后面跟上一个变量,这个id变量将来从数据库中获取
    
    现在的:
    //路由配置文件中
    {
        path: '/wanyi/:id',	//这里的:id是变量,接收传递的id值,本例中id=keyi
        name: 'Wanyi',
        component: Wanyi	//只要路径匹配,就加载Wanyi组件,我们还可以在Wanyi组件中拿到id值
    }
    //App.vue
    <router-link :to="'/wanyi/'+id" active-class="yiwan">科一</router-link>
    <script>
        export default {
            name: 'App',
                data(){
                return {
                    id: 'keyi'    //到时候去数据库查出来的数据
                }
            }
    	}
    </script>

    那么,如何在组件中,拿到路径中携带的参数呢?就是上面的例子,Wanyi组件中如何拿到id值?

    //修改Wanyi.vue
    <h3>{{userId}}</h3>     //你也可以直接{{$route.params.id}}
    export default {
        name: "Wanyi",
        data(){
            return {
                message: '万一'
            }
        },
        methods: {
            say(){
                console.log("测试vue-router");
            }
        },
        computed:{    //计算属性
            userId(){
                return this.$route.params.id //关键
            }
        }
    }
    /*
    	这个$route和$router类似,能在组件中使用,因为在vue-router中,将对象赋值给了Vue原型对象
    	$router代表我们创建的router路由实例,在方法中使用this.$router.push方法不仅可以跳转某个url
    	还可以携带参数到被激活的组件,而我们这$route则是获取当前路由的数据的,当前路由就是当点击标签后
    	会根据标签to属性匹配路由表中对应的路径,匹配成功的路径所在的对象就是当前路由对象。
    */
  • 路由的懒加载
    当打包构建应用时,javascript包会变得非常大,影响页面加载,如果我们能把不同路由对应的组件分割成不同的代码块(JS文件)
    然后当路由被访问的时候才加载对应组件JS文件,这样就会更加高效
    路由中通常有很多组件,这些组件通常会被打包成一个js文件,如果组件太多,这个js就会非常大,当用户进入网站时,
    会花费很长时间,甚至出现短暂空白,如何避免呢?使用懒加载,等到激活该组件时,才去加载组件
    路由懒加载:主要作用就是将路由对应的组件打包成一个个js代码块,只有在这个路由被访问到的时候才加载对应的组件

    以前我们写路由配置,总是先导入组件,然后配置到映射对象中,如下:
    	import Home from '../views/Home.vue'
    	const routes = [
    		{
                path: '/home',
                name: 'Home',
                component: Home
             }
        ]
    而懒加载的方式是:
    	{
            path: '/about',
            name: 'About',
            //懒加载
            component: () => import('../views/About.vue')
         }
    当需要的时候才去导入相应的组件,在打包过程中,这种方式会将组件文件独立成JS文件,等需要的时候再加载。

vue-router嵌套路由

嵌套路由指的是,在路由映射时,一个路径下有多个组件,例如,有两个组件都要在/my路径下,
当我访问/my/news时加载news组件,当我访问/my/message时加载message组件,如果像以前我们得
配置两个路径,通过嵌套路由可以将这两个组件都配置在同一路径下,下面是简单的案例:

使用嵌套路由的步骤:
	1)创建对应的子组件,并且在路由映射中配置对应的子路由
        //创建MyNews.vue,创建MyMessages.vue
        //1.1MyNews.vue,主要看看template
        <template>
            <div>
                <ul>
                    <li>新闻1</li>
                    <li>新闻2</li>
                    <li>新闻3</li>
                    <li>新闻4</li>
                </ul>
            </div>
        </template>
        //1.2MyMessages.vue,主要看看template
        <template>
            <div>
                <ul>
                    <li>消息1</li>
                    <li>消息2</li>
                    <li>消息3</li>
                    <li>消息4</li>
                </ul>
            </div>
        </template>
        //1.3在路由表中的父组件中配置子路由
        {
            path: '/my',
            name: 'My',
            component: My,
            children: [
                {
                    path: '',   //默认路由,这里不需要
                    component: ()=> import('../views/MyNews.vue')
                },
                {
                    path: 'news',
                    component: ()=> import('../views/MyNews.vue')
                },
                {
                    path: 'messages',
                    component: ()=> import('../views/MyMessages.vue')
                }
            ]
        }
	2)在组件内部使用<router-view>标签
        //在父组件中使用标签,My.vue
        <template>
            <div>
                <router-link to="/my/news">新闻</router-link>
                <router-link to="/my/messages">消息</router-link>
                <router-view/>
            </div>
        </template>

vue-router参数传递

参数传递指的是,在路由跳转的时候如何传递参数,传递参数主要有两种类型:params和query

  • params方式
    使用params方式进行参数传递,其router-link中to属性值的对象中必须为name属性不能使用path属性,
    不然在目标组件中拿不到params的值,案例如下:

    <router-link :to="{name: 'Article',params:{id:'123456',name:'万一'},query:{id:'123456',name:'万一'}}">跳转</router-link>
    
    这个Article值就是路由表中目标组件的name属性值
    
    请求形成的路径取决于有没有使用query属性进行传参,如果router-link中使用了query属性进行传参
    则请求的路径是/xxx/xxx?id='123456'....,如果没有使用query属性传参,则请求路径就是name指定组件路径
    或者path属性指定的路径。
    
    我们可以在跳转后的组件中通过this.$route.params.id或this.$route.query.id拿到传递的数据
    params属性用来传递数据,就像post请求传递数据,而query将数据拼接到url上,就像get方式传递数据
    
    注意:params方式只能使用name属性进行参数,不能使用path属性
  • query方式
    上面已经有使用query属性进行传参,query属性传参不管是使用name属性还是path属性都可以
    通过this.$route.query.xxx拿到数据,个人感觉query是最好的传参方式,当然router-link使用name属性效果也是一样的
    当然,除了使用router-link方式传递数据,你也可以使用代码的方式,例如下面方式

    //在my.vue中
    <template>
      <div>
        <button @click="toNews">新闻</button>
        <router-link :to="{path: '/my/messages',query: {name: '消息',id: 2}}">消息</router-link>
      </div>
      <router-view/>
    </template>
    
    <script>
    export default {
      name: "My",
      methods: {
        toNews(){
          this.$router.push({path: '/my/news',query: {name: '我的新闻',id: 1}})
        }
      }
    }
    </script>
    
    /*
    	使用query类型在路由传递参数时,有两种方式,通过router-link标签或者通过代码方式传递参数
    	这里$router的push方法可以传递对象,对象中有路径和参数,之前只传了路径
    	所以总结$router用于传递数据,跳转数据,而$route用于接收数据
    	值得注意的是:通过router-link标签传递数据时,to属性需要加上:引号,不然报错
    */
  • 还有一种传递数据的方式,动态路由
    动态路由是在路由表的路径中使用:xxx,进行路径的占位,形成xxx/xxx/123
    这种方式就像rest风格传递数据,上面已经有动态路由的方式,这种方式和params,query并没有关系。
    使用动态路由的方式,我们也可以在目标组件中获取传递的参数,通过this.$route.params.xxx.

vue-router导航守卫

导航守卫指的是:当发生路由跳转时,可以拦截跳转的过程,像拦截器一样的概念,在跳转路由前后做点事
案例:例如我们实现一个需求,当我们点击按钮,切换组件时,将页面的title变成相应的组件名
这里有两种方式实现,第一种使用钩子函数,第二种使用导航守卫

  • 钩子函数
    在组件中使用created属性,该属性值是一个钩子函数,当我们点击切换组件时,被切换的组件被创建,并调用created函数
    在该函数中改变页面的title,从而实现我们的需求,所有组件实现created函数,在其中设置document.title属性即可实现需求

    created() {
        document.title="home"
    }
    //小提示,钩子函数指的是,我们编写的函数等着vue去调用
  • 导航守卫,我们可以利用路由跳转,组件切换时,必须经过拦截函数,才能切换组件这一特性,
    从而改变title值,代码如下:

    //路由表index.js
    const routes = [
        {
            path: '/',
            name: 'Home',
            component: Home
        },
        {
            path: '/about',
            name: 'About',
            //懒加载
            component: () => import('../views/About.vue')
        },
        {
            path: '/wanyi/:id',
            name: 'Wanyi',
            component: Wanyi
        },
        {
            path: '/my',
            name: 'My',
            component: My,
            children: [
                {
                    path: '',
                    component: ()=> import('../views/MyNews.vue')
                },
                {
                    path: '/my/news',	//嵌套路径中,这两种子路径写法都可以
                    component: ()=> import('../views/MyNews.vue')
                },
                {
                    path: 'messages',
                    component: ()=> import('../views/MyMessages.vue')
                }
            ]
        }
    ]
    const router = createRouter({
        routes,
        history: createWebHashHistory()
        // linkActiveClass: 'wanyi'
    })
    
    //在路由跳转之前,执行该函数
    router.beforeEach((to,from,next)=>{     //这个to,和from都是一个个route对象,即路由表中的对象
        document.title=to.matched[0].name;  //使用matched解决路由嵌套问题
        next();
    })
    export default router
    /*
    	上面使用导航守卫调用的方法beforeEach,也叫作前置钩子或者前置守卫,就是说在跳转组件之前被调用
    	还有一个后置钩子afterEach,这个函数是在跳转组件之后被调用,而且并不需要next方法,因为已经跳转完毕了
    	上面这两个函数都是全局守卫,还有路由独享的守卫(路由的beforeEnter等属性),组件内的守卫(在组件中beforeRouteEnter等属性)
    	全局守卫,顾名思义,全局的路由表中只要发生跳转,就会执行拦截函数,而组件内的守卫则时,
    	切换到这个组件前后才会执行拦截函数,路由独享守卫则是只在该路由中,对该路由下的组件有用。
    */

keep-alive的使用

keep-alive主要做组件状态保存的,当我们从一个组件切换到另一个组件,如果我们不进行状态保存,
那么当我们回退到上一个组件时,会重新加载该组件,而如果我们进行状态保存后,当我们回退上一个组件时,会回到原来的状态。

例如我们点外卖,在店铺按钮中点击进入一个店铺,然后我们切换到我的信息,此时再点击店铺按钮时,
还处于之前的店铺,就说明,之前的状态已经被缓存起来了。

keep-alive是vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染
router-view也是一个组件,是vue-router内置的组件,如果直接被包在keep-alive里面,所有路径匹配到的视图组件都会被缓存

组件中有两个钩子函数,activated和deactivated函数,当组件被激活时,activated被调用,当切换到其他组件时deactivated被调用
前提是,router-view被keep-alive包裹住了,即,使用了缓存,如果不使用缓存,这两个钩子函数不会被调用,
不使用缓存,切换组件时,调用的是组件的created和destroyed钩子函数

我们使用keep-alive标签包裹router-view标签后,所有与router-view关联的组件都会被缓存起来
但是我就想这些被缓存起来的组件中,有几个组件不能被缓存起来这个时候,就需要使用到keep-alive的属性

//keep-alive标签属性
include,字符串或正则表达式,只有匹配的组件会被缓存
exclude,字符串或正则表达式,任何匹配的组件都不会被缓存起来

//属性值可以写多个,用逗号隔开,配置好后,则匹配的组件不会被缓存起来
例如:<keep-alive exclude="组件的name属性,组件的name属性"><router-view/></keep-alive>