Vue2-Router


Vue - Router

  1. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
  2. 前端路由:key是路径,value是组件

那么为什么需要Vue中提供的Router呢?

我们知道,在以前的网站当作,页面可以说全部是多页面应用;比如你当前在index.html这个页面,当你点击一个链接之后,浏览器可能会新开一个页签,然后你可能就来到了user.html这个页面,也就是说,在一个web应用系统中,是由多个html页面组成的,你点击不同的链接,就跳转到不同的页面。但是这样也会带来很多问题,最大的一个影响就是页面会抖动,也就是浏览器那个刷新按钮会转动。

那么在Vue项目中,我们知道,一个Vue项目基本上都是单页面应用,也就是只包含一个html页面,也就是index.html页面,当你如果点击链接之后,由于只有一个html页面,不会出现页面的抖动,但是页面的呈现却要变化,这其实是通过展示不同的组件来完成的。

那么如何实现点击不同的链接,呈现出的是不同的组件并且页面也不跳转刷新呢?这就要用到我们的Vue Router了。

1. 基本使用

使用步骤:

  1. 安装Vue Router

    Vue Router其实是一个插件,需要自己去安装。

    注意:在Vue2中你只能安装Router3及其以下版本,在往高了去,其实Vue2就不支持了,要Vue3才支持。

    npm i vue-router@3
    
  2. 应用插件

    要在main.js文件中应用插件:

    import VueRouter from "vue-router"; //引入插件
    Vue.use(VueRouter);//使用插件
    
  3. 编写Router配置项

    之前我们说过,点击不同的链接跳转到不同的组件,对于链接来说,也就是地址(key);对于组件来说,也就是value,key和value的组合才是一个路由(route);多个路由(route)在一起,才组成了路由器(router)。

    现在我们要来配置这些key-value,也就是一个个route,前提是你的相关组件也要准备好。

    现在的项目结构:

    现在不用过多关心组件内容,先把要用的组件准备好,并且,编写Router配置项的时候,我们一般是在src下新建router文件夹,下面再建index.js文件

    配置内容:

    //1. 引入VueRouter
    import VueRouter from "vue-router";
    
    //2. 引入相关的组件
    import Home from "@/components/Home.vue";
    import About from "@/components/About.vue";
    
    //3. 创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
        routes:[
            //初始化时,默认显示页面
            {
                path: "/", 
                redirect: "/about", //重定向到about路径
            },
            {
                path:'/about',
                component:About
            },
            {
                path:'/home',
                component:Home
            }
        ]
    });
    
    //4. 将router保暴露出去
    export default router;
    
  4. router配置

    router构建好了之后,还要配置到main.js文件中去,当你引入了VueRouter插件之后,你就可以在Vue实例身上配置一个全新的配置项:router。

    import Vue from 'vue'
    import App from './App.vue'
    //引入VueRouter插件
    import VueRouter from "vue-router";
    //引入路由器
    import router from "@/router";
    //应用VueRouter插件
    Vue.use(VueRouter);
    
    Vue.config.productionTip = false
    
    new Vue({
      render: h => h(App),
      router:router //配置router配置项
    }).$mount('#app')
    
  5. 标签切换

    将以前点击链接的地方,也就是a标签,换为router-link标签。

    例如以前是:

    <a class="active" href="about.html">about.html</router-link>
    

    上面的href里面的地址没有写全,不过以前基本上都是…页面名称.html结尾。

    现在:

    <router-link active-class="active" to="/about">About</router-link>
    

    其中active-class表示点击时加上的样式to表示要到达的路由key,然后VueRouter会根据这个key找到对应的组件,之后渲染页面。

    上面的标签属性都属于VueRouter里面的。

    注意:再vue重新渲染页面之后,你的router-link标签会被解析为a标签的。

  6. 指定展示位置

    现在可以找到对应的组件了,但是你没有指定这个组件应该放到什么位置,就好像插槽那一章一样,在插槽中,你用slot标签指定插槽放入页面结构的位置,所以在VueRouter中,你也要使用一个标签来存放组件的结构,这里使用router-view标签:

    <router-view></router-view>
    

了解了基本的使用步骤之后,现在写个基本案例看看:

<template>
  <div class="app">
    <div class="nav">
      <a href="javascript:;" @click="clickAbout" :class="{'active': needActive === 'About'}">About</a>
      <hr>
      <a href="javascript:;" @click="clickHome" :class="{'active': needActive === 'Home'}">Home</a>
    </div>
    <div class="content">
      <About v-show="needActive === 'About'"/>
      <Home v-show="needActive === 'Home'"/>
    </div>
  </div>
</template>

<script>

import About from "@/components/About.vue";
import Home from "@/components/Home.vue";

export default {
  name: 'App',
  components: {
    About,
    Home,
  },
  data(){
    return {
      needActive: 'About' //记录当前需要激活active样式的组件
    }
  },
  methods:{
    clickAbout(){
      this.needActive = 'About';
    },
    clickHome(){
      this.needActive = 'Home'
    }
  }
};

</script>

<style>

.app {
  width: 600px;
}

.nav {
  width: 200px;
  border: 1px solid black;
  float: left;
}

.nav a {
  display: block;
  text-align: center;
  text-decoration: none;
  margin-top: 0;
}
.content {
  width: 200px;
  float: left;
}

.active {
  background-color: skyblue;
}
</style>

执行结果:

通过点击不同的导航栏可以在右侧展示不同的内容。注意,按照以前传统的方式来说,不同链接的地址应该为对应的html页面, 只是我们这里做了特殊处理,因为是在Vue项目中编写的,所有没有这么多html,不过意思是这么个意思。并且如果你的链接的地方写了不同的html页面之后,浏览器是会刷新的

现在要改为VueRouter的形式,我们注意观察地址栏和浏览器是否刷新:

<template>
  <div class="app">
    <div class="nav">
      <router-link to="/about" active-class="active">About</router-link>
      <hr>
      <router-link to="/home" active-class="active">Home</router-link>
    </div>
    <div class="content">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>

export default {
  name: 'App'
};

</script>

<style>
/*样式文件不变*/
</style>

执行结果:

可见页面默认展示的时About组件,并且,端口号后面有一个#号,这是和VueRouter的路由模式有关,这里先不做探讨。

并且对于router-link标签,他底层其实又转为了a标签

然后以前写选择样式的时候,点击了某个连接,导航栏的背景就要变,以前时通过写点击事件来动态绑定样式的,现在你只需要使用active-class标签属性就能够实现样式的切换。并且对于页面的呈现,使用router-view标签即可。

2. 几个注意点

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

    就好比之前我们的AboutHome组件,都属于路由组件,也就是index.js中配置了的组件,都属于路由组件

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

    改造Home组件:

    export default {
        name: 'Home',
        mounted() {
          console.log('Home组件挂载了....');
        },
        beforeDestroy() {
          console.log('Home组件销毁了....');
        }
    }
    

    改造About组件:

    export default {
        name: 'About',
        mounted() {
          console.log('About组件挂载了....');
        },
        beforeDestroy() {
          console.log('About组件销毁了....');
        }
    }
    

    执行结果:

    当我切换为Home的时候注意查看:

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

    我们在路由组件挂载的时候看看this上面都有啥。

    每一个路由组件上都有route,并且不同的路由组件里面存放的信息是不一样的

    mounted() {
      console.log('About组件挂载了....',this);
      window.aboutRoute = this.$route;
      window.aboutRouter = this.$router;
    }
    
    mounted() {
      console.log('Home组件挂载了....',this);
      window.homeRoute = this.$route;
      window.homeRouter = this.$router;
    }
    

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

    对于上面的也看见了,虽然路由组件不同,但是他们的router是相同的。

3. 嵌套路由(多级路由)

  1. 配置路由规则,使用children配置项:

    routes:[
        {
            path:'/about',
            component:About,
        },
        {
            path:'/home',
            component:Home,
            children:[ //通过children配置子级路由
                {
                    path:'news', //此处一定不要写:/news
                    component:News
                },
                {
                    path:'message',//此处一定不要写:/message
                    component:Message
                }
            ]
        }
    ]
    
  2. 跳转(要写完整路径):

    <router-link to="/home/news">News</router-link>
    

注意:在多级路由的时候,子路由(children)中的path千万不要写/

4. 路由的query参数

我们都知道,在一个网址路径后面,是可以使用?xxx=yyy条件形式的,并且多个条件之间使用&分隔。

例如:https://demo.net/survey/detail?id=440;其中的id=440就是条件,条件与路由使用?分隔。

在VueRouter中使用query参数:

  1. to的字符串写法

    <router-link to="/about?id=666&title=关于" active-class="active">About</router-link>
    

    可见可以直接在路径后面跟上条件即可。执行结果:

    可见传参成功,并且在route中有对应的query配置。

    上述是传递静态的参数,动态的参数传递如下:

    <router-link :to="`/about?id=${id}&title=${title}`" active-class="active">About</router-link>
    
    export default {
      name: 'App',
      data() {
        return {
          id: '666',
          title: '关于'
        }
      }
    };
    

    执行结果也为一样,不过要注意的是,传递动态参数的时候里面是用的反引号,并且是v-on:to

  2. to的对象写法

    除了字符串写法,还有一种是对象写法:

    <router-link
      :to="{
        path:'/about',
        query:{
          id:666,
          title:'关于'
        }
      }"
      active-class="active"
    >
    About
    </router-link>
    

    新增了两个配置参数:pathquery

    其中path还是正常的写路径即可,query处就写你想要传递的参数信息。注意,这里的to也是需要写成动态绑定的形式:to

接收参数

想要获取到query参数也很简单:

$route.query.id
$route.query.title

5. 命名路由

顾名思义就是给路由取名字,作用:可以简化路由的跳转。

使用步骤:

  1. 给路由取名

    const router = new VueRouter({
        routes:[
            //初始化时,默认显示页面
            {
                path: "/",
                redirect: "/about", //重定向到about路径
            },
            {
                name:'guanyu', //给路由命名
                path:'/about',
                component:About
            },
            {
                name:'zhuye', //给路由命名
                path:'/home',
                component:Home
            }
        ]
    });
    
  2. 简化使用

    <router-link
      :to="{
        name:'guanyu',
        query:{
          id:666,
          title:'关于'
        }
      }"
      active-class="active"
    >
    About
    </router-link>
    <hr>
    <router-link :to="{name:'zhuye'}" active-class="active">Home</router-link>
    

    对于很长的路径那种情况,使用路由名称能够很好的简化。

6. 路由的params参数

之前说了路由的query参数,现在来看看路由的parmas参数。

使用步骤:

  1. 配置路由,声明接收params参数

    //3. 创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
        routes:[
            //初始化时,默认显示页面
            {
                path: "/",
                redirect: "/about", //重定向到about路径
            },
            {
                name:'guanyu',
                path:'/about',
                component:About
            },
            {
                name:'zhuye',
                path:'/home/:id/:title',//使用占位符声明接收params参数
                component:Home
            }
        ]
    });
    
  2. 传递参数

    to的字符串写法

    <router-link to="/home/666/首页" active-class="active">Home</router-link>
    

    to的对象写法

    <router-link
      :to="{
        name:'zhuye',
        params:{
          id:666,
          title:'首页'
        }
      }"
      active-class="active"
    >
    Home
    </router-link>
    

    执行结果:

    可见params配置项中有值了。

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

7. 路由的props配置

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

以前我们学了父子组件之间使用props传递数据,现在在路由这里,也可以用props配置传递数据。

props配置是写在路由配置中的,也就是index.js文件中,并且写在哪个路由里面,就代表传递数据给哪个组件。

例如:

const router = new VueRouter({
    routes:[
        //初始化时,默认显示页面
        {
            path: "/",
            redirect: "/about", 
        },
        {
            name:'guanyu',
            path:'/about',
            component:About,
            props:{a:1,b:'哈哈'} //使用props配置项传递数据给About组件
        },
        {
            name:'zhuye',
            path:'/home/:id/:title',
            component:Home
        }
    ]
});

About.vue

<template>
  <div>
    我是About中的内容....<br/>
    我收到的数据:a={{a}}; b={{b}}
  </div>
</template>

<script>
  export default {
    name: 'About',
    props:['a','b'], //注意:这里仍然是需要用props来接收的。
    mounted() {
      console.log('About组件挂载了....',this);
    }
  }
</script>
<style scoped>
</style>

执行结果:


props配置一共有三种写法:

  1. props值为对象,该对象中所有的key-value的组合最终都会通过props传给组件

    {
        name:'guanyu',
        path:'/about',
        component:About,
        props:{a:1,b:'哈哈'} //使用props配置项传递数据给About组件
    },
    

    这种刚刚已经演示过了,就不再演示了。

  2. props值为布尔值,布尔值为true,则把路由收到的所有**params**参数通过props传给组件

    {
        name: 'zhuye',
        path: '/home/:id/:title',//使用占位符声明接收params参数
        component: Home,
        props: true //使用props配置项传递数据给Home组件
    }
    

    Home.vue

    <template>
      <div>
        我是Home中的内容....
        收到的数据为:id={{id}};title={{title}}
      </div>
    </template>
    
    <script>
      export default {
        name: 'Home',
        props:['id','title'],
        mounted() {
          console.log('Home组件挂载了....',this);
        }
      }
    </script>
    <style scoped>
    </style>
    

    执行结果:

  3. props值为函数,该函数返回的对象中每一组key-value都会通过props传给组件

    props(route) {
        return {
            id: route.params.id,
            title: route.params.title,
        }
    }
    

    同样能够收到数据。

    说明:如果props值为函数的时候,能够收到一个参数,这个参数就是每个路由组件的route对象。如果你是通过query传递的参数,那么再props函数中,你就要用route.query.xxx,如果是params传递的参数,那么就用route.params.xxx

8. <router-link>的replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式

    也就是通过浏览器上面两个箭头来操作的浏览器历史记录。

  2. 浏览器的历史记录有两种写入方式:分别为pushreplacepush追加历史记录replace替换当前记录。路由跳转时候默认push

    对于浏览器来说,默认就是push的方式,也就是将你的浏览记录依次入栈,然后通过一个指针来访问浏览记录。类似于下图:

    通过浏览器的左箭头就是回退,右箭头就是前进。

    如果你是用的replace模式,那么就是这样的场景:

    新来的记录会将以前的记录给**替换(覆盖)**,所以,此时你无法回退也无法前进。

  3. 如何开启replace模式

    直接再router-link标签上写上replace标签属性即可。

    <router-link :to="/home" active-class="active" replace>Home</router-link>
    

9. 编程式路由导航

以前我们使用路由跳转使用的是router-link标签,但是我们知道,router-link在被Vue解析了之后,会变成普通的a标签,所以,一般我们再写路由的时候,明确知道是通过链接来实现的时候,一般我们就用router-link就可以了,但是有时候我们想实现点击一个按钮Button标签、块div标签等来实现路由的切换,这时候就不可以使用router-link了,那这时候该使用什么东西来完成路由的切换呢。

所以,编程式路由导航就是这样产生的,作用:不借助<router-link> 实现路由跳转,让路由跳转更加灵活

我们可以使用$router身上的一些API来实现任意路由的切换:

  1. this.$router.push

    对应浏览器记录的push模式,通过向浏览器记录栈中追加你输入的URL:

    this.$router.push({
        name:'路由名称',
            params:{
                id:xxx,
                title:xxx
            }
    })
    
  2. this.$router.replace

    对应浏览器记录的replace模式,将浏览器记录栈中当前的URL替换为你输入的URL:

    this.$router.replace({
        name:'xiangqing',
            params:{
                id:xxx,
                title:xxx
            }
    })
    

以上两种方式可以导航到任意的路由,并且不仅限于a标签可使用,所有的标签都可以。

另外还有一些API可以实现浏览器的回退和前进按钮:

this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退,可接受参数,参数的值就是回退或前进的步数

10. 缓存路由组件

有时候我们有这种情况:

当我们在一个路由组件中输入了一些值,然后切换了路由之后再回来的时候,输入框中的值就被清空了。这是因为当你切走路由组件的时候,对应的组件会被销毁,然后切进路由组件的时候,对应的组件会被创建。

所以,如果你想要保存下输入框的值,你就要保证切换路由组件的时候,保证前一个组件不被销毁,这就是缓存路由组件的作用。

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

通过keep-alive标签实现缓存路由组件,通过include标签属性来指定要缓存的路由组件。

例如:

<keep-alive include="About"> 
    <router-view></router-view>
</keep-alive>

注意:

  1. keep-alive标签是写再router-view标签外层的,不是写在其他标签外层。
  2. 如果不写include属性标签表示缓存所有的组件。
  3. 如果是数组::include="['About','Home']"

可见当离开About路由,进入Home路由的时候,About组件被挂起了,并为被销毁,反观Home组件切回来的时候被销毁了。

11. 两个新的生命周期钩子

之前我们学习了Vue的生命周期钩子,但是没有学完,这里再学两个新的生命周期钩子。

作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。

现在我有这样的需求:在About路由组件被缓存的前提下,要求切入About路由组件的时候,定时器执行,离开About路由组件切进Home路由组件的时候定时器销毁。

如果使用已有的Vue生命周期钩子,代码如下:

About.vue

mounted() {
  console.log('About组件挂载了....定时器要开始执行');
  this.timerId = setInterval(()=>{
    console.log('@');
  },50);
},
beforeDestroy() {
  console.log('About组件将要被销毁了....定时器要被销毁')
  clearInterval(this.timerId);
}

执行结果可见,当我切走About组件的时候,由于该路由组件被缓存了,并没有执行beforeDestroy方法,这时候应该怎么办呢,我既想要保留下输入框中的值,又想在切走该路由组件的时候停止这个定时器。

这时候就要引出两个新的生命周期钩子了:

  1. activated路由组件被激活时触发,也就是切入时。
  2. deactivated路由组件失活时触发,也就是切出时。

改造About.vue代码:

activated() {
  console.log('About组件被激活了....定时器要开始执行');
  this.timerId = setInterval(()=>{
    console.log('@');
  },50);
},
deactivated() {
  console.log('About组件失活了了....定时器要被销毁')
  clearInterval(this.timerId);
}

这时候你再去测试,发现About路由组件切走的时候定时器被销毁了,但是你切回来的时候,输入框中的内容并没有丢失,定时器也重新开启了。

12. 路由守卫

作用:对路由进行权限控制;例如哪些用户可以查看这个页面。

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

12.1 全局守卫

全局路由守卫配置是放到路由器配置文件中的,也就是index.js中。

全局路由守卫中,又分为前置路由守卫和后置路由守卫:

  1. 前置路由守卫

    你构建的VueRouter是什么名称这里就用名称.beforeEach

    例如我构建VueRouter时:

    //3. 创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
        routes: [
            //初始化时,默认显示页面
            {
                path: "/",
                redirect: "/about", //重定向到about路径
            },
            {
                name: 'guanyu',
                path: '/about',
                component: About,
                props: {a: 1, b: '哈哈'}
            },
            {
                name: 'zhuye',
                path: '/home/:id/:title',//使用占位符声明接收params参数
                component: Home,
                props(route) {
                    return {
                        id: route.params.id,
                        title: route.params.title,
                    }
                }
            }
        ]
    });
    

    我使用的时router实例对象,那么这里我就使用router.beforeEach来进行前置路由守卫。

    其回调函数可以收到3个参数,分别为tofromnext

    • to表示从哪里来,来至于哪个路由
    • from表示到哪去,值为目标路由地址
    • next表示放行操作

    例如:

    //全局前置守卫:初始化时执行、每次路由切换前执行
    router.beforeEach((to,from,next)=>{
        console.log('beforeEach',to,from)
        if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
            if(localStorage.getItem('school') === 'B站大学'){ //权限控制的具体规则
                next() //放行
            }else{
                alert('暂无权限查看')
                // next({name:'guanyu'})
            }
        }else{
            next() //放行
        }
    })
    

    补充:

    我上述代码中使用了metameta代表的是路由元数据,一般是程序员自己写的;每个路由都有一个 meta 元数据字段, 我们可以在这里设置一些自定义信息,供页面组件或者路由钩子函数中使用。

    前置路由守卫执行时机

    • 初始化时执行
    • 每次路由切换前执行
  2. 后置路由守卫

    参考前置路由守卫,对应的,这里还有后置路由守卫,使用afterEach这个API,其回调函数只能接收两个参数:tofrom

    这两个参数的含义与前置路由一致。

    //全局后置守卫:初始化时执行、每次路由切换后执行
    router.afterEach((to,from)=>{
        console.log('afterEach',to,from)
        if(to.meta.title){ 
            document.title = to.meta.title //修改网页的title
        }else{
            document.title = 'vue_test'
        }
    })
    

1.2 独享守卫

独享守卫是写在了具体某个路由中,只为该路由服务,对应的API为beforeEnter,例如:

{
name: 'guanyu',
path: '/about',
component: About,
props: {a: 1, b: '哈哈'},
meta: {isAuth: true}, //自定义数据
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()
        }
    }
},

注意:独享路由守卫虽然API是beforeEnter,但是并没有afterEnter。

1.3 组件内守卫

组件内守卫是配置到具体的组件中的。组件内守卫又分为进入守卫和离开守卫

  1. 进入守卫

    通过路由规则进入该组件时被调用

    //进入守卫:通过路由规则,进入该组件时被调用
    beforeRouteEnter (to, from, next) {
    },
    
  2. 离开守卫

    通过路由规则离开该组件时被调用

    //离开守卫:通过路由规则,离开该组件时被调用
    beforeRouteLeave (to, from, next) {
    }
    

注意:beforeRouteEnterbeforeRouteLeave这两个方法和生命周期钩子,datacomponents配置项都是平级的关系。

13. 路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。

  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器

    也就是你请求数据的时候,不会把#号后边的内容当作地址发送给服务器请求数据

  3. hash模式:

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

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

配置方式:

在构建路由器的时候,有一个mode配置项:

//3. 创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
    mode: 'history', //配置为history模式,不写默认为hash模式
    routes: [
          //众多路由...
    ]
});

文章作者: 念心卓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 念心卓 !
  目录