Vue-Router 笔记

138 阅读4分钟

路由的基本使用

  • 下载
    • npm install vue-router or yarn add vue-router
  • 引入
    • import VueRouter from 'vue-router'
    • import router from './router'
  • 应用
    • Vue.use(VueRouter)
  • 配置
        new Vue({
            render: h =>h(App),
            router
        }).$mount('#app')
    
  • 配置 Router 文件
        // router/index.js  用于创建管理整个应用的路由器
        import VueRouter from 'vue-router'
        
        // 引入组件
        import About from '../components/About.vue'
        import Home from '../components/Home.vue'
        
        // 创建一个路由器
        export default new VueRouter({
            routes: [
                { // 一级路由
                    name: 'About',
                    path: '/about',
                    component: About
                },
                { // 以及路由
                    name: 'Home',
                    path: '/home',
                    component: Home
                },
            ]
        })
    
  • 模板
        <!-- 切换按钮,最终会转换成 <a> 标签 -->
        <!-- active-class 属性为选中后的 css 样式 -->
        <router-link to="/about" active-class="select">About</router-link>
        <router-link to="/link" active-class="select">Home</router-link>
        
        <!-- 展示区 -->
        <router-view></router-view>
    
    

几个注意点

  • 通过路由渲染的组件叫路由组件,反之(通过组件标签渲染)为普通组件,路由组件最好放在单独的文件夹里统一管理,比如 pages 文件夹
  • 当前未展示的路由组件,默认是被销毁的,当需要渲染时,再重新挂载
  • 参与了路由配置的路由组件,可以调用$route$router$route为当前组件的路由配置信息,$router为全局路由器

嵌套路由

  • 一级路由里面可以配置二级路由,同理,二级路由下面可以配置三级路由,以此类推
        ...
        import News from '../pages/News.vue'
        import Message from '../pages/Message.vue'
        
        export default new VueRouter(
            route:[
                ...
                {
                    name:'Home',
                    path: '/home',
                    component: Home,
                    // 二级路由
                    children: [
                        {
                            name: 'News',
                            path: 'news',
                            component: News
                        },
                        {
                            name: 'Message',
                            path: 'message',
                            component: Message
                        }
                    ]
                }
            ]
        )
    
        <!-- Home 组件 -->
        <template>
            <router-link active-class="select" to="/home/news">news</router-link>
            <router-link active-class="select" to="/home/message">message</router-link>
            <router-view><router-view>
        </template>
    

路由传参 路由的 query 参数

  • 从三级路由组件 Detail 中展示二级路由组件 Message 的详细信息
  • 路由配置
        import Detail from '../pages/Detail.vue'
        ...
        {   // 二级路由
            name: 'Message',
            path: 'message',
            component: Message
            children: [
                {   // 三级路由
                    name: 'Detail',
                    path: 'detail',
                    component: Detail
                }
            ]
        }
        ...
    
  • Message 路由组件中传递 query 参数的两种写法
        <template>
            <div>
                <ul>
                    <li v-for="m in messageList" :key="m.id">
                        <!-- 传递 query 参数,字符串写法 -->
                        <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">消息1</router-link>
                        <!-- or -->
                        <!-- 传递 query 参数,对象写法 -->
                        <router-link :to="{
                            path:'/home/message/detail',
                            query:{
                                id: m.id,
                                title: m.title
                            }
                        }">消息1</router-link>
                    </li>
                </ul>
            </div>
        </template>
    
        <script>
            export default {
                name: 'Message',
                data(){
                    return {
                        messageList:[
                         {id:'001',title:'详情001'},
                         {id:'002',title:'详情002'},
                         {id:'003',title:'详情003'}
                        ]
                    }
                }
            }
        </script>
    
  • Detail 路由组件通过 $route 接收 query 参数
        <template>
           <ul>
               <li>消息编号: {{$route.query.id}}</li>
               <li>消息标题: {{$route.query.title}}</li>
           </ul> 
        </template>
    

命名路由

  • 当路由出现多级嵌套时,嵌套层级越多,子级路由的路径 path 就会变的越长
  • 当路由配置了 name 属性时,在多级路由里就可以直接使用 name 来代替 path 指定渲染的目标
  • 如上代码
            ...
        <!-- 传递 query 参数,对象写法 -->
                        <router-link :to="{
                            name: 'Detail',
                            query:{
                                id: m.id,
                                title: m.title
                            }
                        }">消息1</router-link>
            ...
    

路由的 params 参数

  • 要使用 params 传递参数,就要在路由器的配置里使用占位符,来告诉路由器,携带参数的位置
        import Detail from '../pages/Detail.vue'
        ...
        {   // 二级路由
            name: 'Message',
            path: 'message',
            component: Message
            children: [
                {   // 三级路由
                    name: 'Detail',
                    path: 'detail/:id/:title', // 使用占位符
                    component: Detail
                }
            ]
        }
        ...
    
  • 模板中携带参数时,直接拼接到 path 后面
  • 如果使用对象写法,指定目标必须用 name
         <!-- 传递 params 参数,字符串写法 -->
           <router-link :to="`/home/message/detail/${m.id}/${m.title}`">消息1</router-link>
                 <!-- or -->
                 <!-- 传递 params 参数,对象写法 -->
           <router-link :to="{
                     name:'Detail',
                     params:{
                          id: m.id,
                          title: m.title
                      }
             }">消息1</router-link>
    
    
  • Detail 路由组件中获取 params 参数
        <template>
            <ul>
                <li>消息编号:{{$route.params.id}}</li>
                <li>消息标题: {{$route.parmas.title}}</li>
            </ul>
        </template>
    

路由的 props 配置

  • props 可以解决路由组件中接收 params 和 query 参数时,由于参数过多造成的代码重复的问题
  • props 配置有三种写法
        import Detail from '../pages/Detail.vue'
        ...
        {   // 二级路由
            name: 'Message',
            path: 'message',
            component: Message
            children: [
                {   // 三级路由
                    name: 'Detail',
                    path: 'detail/:id/:title', // 使用占位符
                    component: Detail
                    // 对象写法
                    props: {a: 1, b: 2}
                    // 布尔写法
                    props: true
                    // 函数写法
                    props($route){
                        return {id: $route.query.id, title: $route.query.title}
                    }
                }
            ]
        }
        ...
    
  • 对象写法:该对象中所有的 key、value 都会以 props 的形式传递给 Detail 组件
  • 布尔写法:若布尔值为真,就会将该路由组件收到的所有params参数,传给 Detail 组件,注意不会传递 query 参数
  • 函数写法:vue-router 会将 $route以参数的形式传递给函数,在函数中,可以借助$route获取任意参数,并通过返回值以 props 的形式传递给 Detail 组件
  • 最终, Detail 组件通过 props 接收参数后的代码如下
        <template>
            <ul>
                <li>消息编号:{{id}}</li>
                <li>消息标题: {{title}}</li>
            </ul>
        </template>
        <script>
            export default {
                name: 'Detail',
                props: ['id','title']
            }
        </script>
    

router-link 的 replace 属性

  • router-link 标签的每次点击都会形成历史记录,默认为 push 模式
  • 对历史记录的操作模式除了 push,还有 replace 模式,它与 push 模式最大的不同就是 replace 会替换掉当前栈顶的那条记录
  • 开启 replace 模式:
        <router-link :replace="true" to="/xx">...</router-link>
        <!-- or -->
        <router-link replace to="/xx">...</router-link>
    

编程式路由导航

  • router-link 标签在模板渲染后最终会成为 a 标签,当有特殊需求不能使用 a 标签时,就需要使用其他方案代替 router-link

  • 使用 button 标签实现路由跳转:

    <template>
        ...
        <li v-for="m in messageList" :key="m.id">
            ...
            <button @click="pushShow(m)">push查看</button>
            <button @click="replaceShow(m)">replace查看</button>
        </i>
        ...
        <router-view></router-view>
    </template>
    
        ...
        methods:{
           pushShow(m){
               this.$router.push({
                   name:'Detail',
                   params: {
                       id: m.id,
                       title: m.title
                   }
               })
           },
           replaceShow(m){
               this.$router.replace({
                   name:'Detail',
                   params: {
                       id: m.id,
                       title: m.title
                   }
               })
           }
        }
        ...
    
  • 除了 button,任何一个可以绑定并触发事件的元素,都可以实现编程式路由

缓存路由组件

  • 当路由从当前渲染组件切到其他路由组件时,当前被切出的路由组件将会被销毁。也就是说,即将被销毁的路由组件,无法保存用户所输入的任何内容。
  • 使用 keep-alive 标签,将不希望被销毁的路由组件的展示区(router-view)包裹起来,可以实现路由组件不会被销毁
        <template>
            ...
            <router-link to="...">...</router-link>
            <!-- 如果不配置 include 那么标签内的所有路由组件都将被缓存 -->
            <!-- 如果需要配置多个组件名,那么使用数组 :nclude="['名1','名2']" -->
            <keep-alive include="组件名">
                <router-view></router-view>
            </keep-alive>
            ...
        </template>
    

路由组件独有的生命周期钩子,用于捕获路由组件的激活状态

  • activated
    • 当前路由组件激活(渲染)时触发
  • deactivated
    • 当前路由组件失活(被跳转)时触发

路由守卫

全局前置路由守卫
  • beforeEach
    • 全局前置路由守卫,在第一次初始化和每一次路由切换之前被调用
    • 三个参数, to 为目标组件,from 为起始组件, next 为放行函数
        ...
        const router = new VueRouter({
                routes:[
                    {
                        name: 'News',
                        path: '/news',
                        component: News
                    },
                    {
                        name: 'Message',
                        path: '/message',
                        component: Message
                    }
                ]
        })
        
        router.beforeEach(()=>{
            if (to.name === 'News' || to.name === 'Message') {
                if (localStorage.getItem('myId') === 'ok') {
                    next()
                } else {
                    alert('用户名或者密码错误,登录失败')
                }
            } else {
                next()
            }
        })
        
        export default router
    
  • 利用 name 或者 path 来判断路由是否被校验时,如果需要被校验的路由过多,那代码就会变得繁琐,我们可以在需要被校验的路由里的路由元信息meta里配置一个布尔值,通过布尔值来判断
        ...
        const router = new VueRouter({
                routes:[
                    {
                        name: 'News',
                        path: '/news',
                        component: News,
                        meta:{isAuth:true}
                    },
                    {
                        name: 'Message',
                        path: '/message',
                        component: Message,
                        // 如果不需要校验,也可以不写,因为 null 也是false
                        meta:{isAuth: false}
                    }
                ]
        })
        
        router.beforeEach(()=>{
            if (to.mata.isAuth){
                if (localStorage.getItem('myId') === 'ok') {
                    next()
                } else {
                    alert('用户名或者密码错误,登录失败')
                }
            } else {
                next()
            }
        })
        
        export default router
    
全局后置路由守卫
  • router.afterEach
    • 两个参数 to、 from
    • 常用来自动切换页面的 title
        ...
        router.afterEach((to, from) => {
            document.title = to.name
        })
    
独享路由守卫
  • beforeEnter
  • 顾名思义,就是单独在指定的路由内配置的路由守卫
        ...
        export default  new VueRouter({
                routes:[
                    {
                        name: 'News',
                        path: '/news',
                        component: News,
                        beforeEnter:(to,from,next)=>{
                            if (localStorage.getItem('myId') === 'ok') {
                                next()
                            } else {
                                alert('用户名或者密码错误,登录失败')
                            }
                        }
                    },
                    {
                        name: 'Message',
                        path: '/message',
                        component: Message,
                    }
                ]
        })
    
    
组件内路由守卫
  • beforeRouteEnter(to, from, next){}
    • 通过路由规则进入该组件时被调用
  • beforeRouteLeave(to, from, next){}
    • 通过路由规则离开该组件时被调用

路由器的两种基本工作模式

哈希模式
  • vue-router 默认开启的是 hash 模式,当使用哈希模式时,浏览器的路径里会自动携带一个 ## 后面的内容不会通过 http 请求发送给服务器
history 模式
  • 要开启路由器的 history 模式,需要在创建路由器时,配置 mode
        ...
        export default new VueRouter({
            mode: 'history',
            routes:[{...}]
        })
    
  • history 的兼容性略差
  • history 模式下,路径不会携带 #,在项目上线之前,这一点尤为不同。由于 history 模式不会携带 #,所有由前端路由产生的路径在页面刷新时,就会被浏览器当作服务器的资源发起 http 请求,如果服务器没有这些资源,就会报错
    • 有多种解决办法,比如后端将前端路由产生的路径在服务器端进行比对,比如 node.js 的 connect-history-api-fallback 中间件,就是专门为了解决这个问题的
  • hash 模式在通过第三方手机 app 分享时,若 app 校验严格则地址会因为 #而被校验为不合法,history 模式就避免了这个问题