美文网首页
Vue路由总结

Vue路由总结

作者: 李牧敲代码 | 来源:发表于2019-03-14 20:57 被阅读0次

    Vue Router 官网已经写得很好了,这里自己再总结巩固下。(注意:这里所有的例子都是基于Vue/cli 3.4的脚手架进行讲解的)
    Vue的路由分两种:编程式路由声明式路由

    vm.$route.push('/路由地址')  //编程式路由
    
    <router-link to="/路由地址"></router-link>   //声明式路由
    

    Vue Router可以分以下几个模块来讲解

    • 动态路由匹配
    • 嵌套路由
    • 编程式导航
    • 命名路由
    • 命名视图
    • 重定向和别名
    • 路由组件传参
    • H5 History模式
    • 导航守卫
    • 路由元信息
    • 过渡动效(本文不做介绍)
    • 数据获取
    • 滚动行为
    • 路由懒加载

    动态路由匹配

    【应用场景】

    假设我们有个模板是展示用户信息的,在上一个模板中,点击不同的用户ID进入相同的模板,但是展示的用户信息是不一样的。就像这样:
    /user/user1/user/user2,当然还有其他方式,我们先用这种方式。看代码:

    <!-- 先写一个视图用于放置声明式路由 -->
    <template>
        <div>
            home
            <router-link to="/componentA/user1">user1</router-link>
            <router-link to="/componentA/user2">user2</router-link>
        </div>
    </template>
    
    <script>
        // @ is an alias to /src
        export default {
    
        }
    </script>
    
    
    //在脚手架里的router.js里配置路由
    import Vue from 'vue'
    import Router from 'vue-router'
    import Home from './views/Home.vue'
    import componentA from './components/componentA.vue'
    
    Vue.use(Router)
    
    export default new Router({
        mode: 'history',
        base: process.env.BASE_URL,
        routes: [
            {
                path: '/',
                name: 'home',
                component: Home
            },
            {
                path: '/componentA/:id/', //这里就是动态路由,通过id来进行动态匹配
                component: componentA
            }
        ]
    })
    
    <!-- componentA -->
    <template>
        <div>
            componentA: {{$route.params.id}}
            <button @click="goBack">goBack</button>
        </div>
    </template>
    <script>
        export default {
            name: 'componentA',
            methods: {
                goBack() {
                    this.$router.push('/')
                },
            }
        }
    </script>
    <style lang="">
    
    </style>
    
    image
    【响应路由参数变化注意点】
    1. 组件会被复用
    2. 组件生命周期钩子不会被调用
    3. 可以通过 watch方法或者 beforeRouteUpdate监听动态路由变化
        watch() {
            '$route'(to, from) {
                //...
            }
        },
        beforeRouteUpdate(to, from, next) {
            console.log("to", to);
            console.log("from", from);
            console.log("next", next);
        },
    

    嵌套路由

    【应用场景】

    比如做个tab页,就像下图这样:


    test.gif

    这个时候就要用到嵌套视图了,看代码:
    现在App.vue里放个路由视图(也就是<router-view></router-view这个组件)

    <template>
    <!-- App.vue -->
      <div id="app">
        <router-view></router-view>
      </div>
    </template>
    
    <style lang="less">
    #app {
      font-family: 'Avenir', Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
    }
    #nav {
      padding: 30px;
      a {
        font-weight: bold;
        color: #2c3e50;
        &.router-link-exact-active {
          color: #42b983;
        }
      }
    }
    </style>
    

    先写一个组件充当父路由的组件

    <template>
    <!-- tab.vue -->
        <div class="tab">
            <router-link to="/index/a">首页</router-link>
            <router-link to="/index/b">技术页</router-link>
            <!-- 这里很重要,如果父路由里的组件里没有相应的路由视图,那么子路由导航后是无法展现的 -->
            <router-view></router-view>
        </div>
    </template>
    
    <script>
    import Vue from 'vue';
    // @ is an alias to /src
    export default {
        name: 'tab',
    };
    </script>
    <style scoped>
        .tab {
            background-color: yellow;
        }
    </style>
    
    
    

    然后随便写2个组件视图

    <template>
    <!-- componentA_a.vue -->
        <div class="index">
            <div class="index">
                首页
            </div>
            
        </div>
    </template>
    <script>
    export default {
        name: "componentA_a",
    };
    </script>
    <style scoped>
        .index {
            background-color: red;
        }
    </style>
    
    <template>
    <!-- componentA_b.vue -->
        <div class="tec">
            <div class="tec">技术页</div>
        </div>
    </template>
    <script>
    export default {
        name: "componentA_b",
        created() {
            console.log(123);
        }
    };
    </script>
    <style scoped>
    .tec {
        background-color: lightblue;
    }
    </style>
    

    最后配置router.js

    import Vue from 'vue'
    import Router from 'vue-router'
    import tab from './views/tab.vue'
    
    Vue.use(Router)
    
    export default new Router({
        mode: 'history',
        base: process.env.BASE_URL,
        routes: [
            {
                path: '/',
                redirect: '/index' //重定向到/index路由
            },
            {
                path: '/index',
                component: tab,
                children: [
                    {
                        path: '',
                        redirect: 'a' //重定向路由,当路径是空的时候可以重定向路由也可以提供一个组件
                    },
                    {
                        path: 'a',
                        name: "componentA_a",
                        component: () => import('./components/componentA_a.vue')                    
                    },
                    {
                        path: 'b',
                        name: "componentA_b",
                        component: () => import('./components/componentA_b.vue')                    
                    }
                ]
            }
        ]
    })
    

    这样就实现了嵌套路由,效果就是前面那张图。
    PS:

    • 以 / 开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。
    • 如果父路由里的组件里没有相应的路由视图,那么子路由导航后是无法展现的

    编程式导航

    【应用场景】

    大多数情况还是编程式导航的吧,毕竟编程式导航用起来更加灵活。我可以对任务组件绑定事件,触发路由导航,而且可以随性所欲的传参。
    3个方法

    • router.push(location, onComplete?, onAbort?)

    想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
    PS: <router-link :to="..."> 等同于调用 router.push(...)。
    该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:

    // 字符串
    router.push('home')
    
    // 对象
    router.push({ path: 'home' })
    
    // 命名的路由
    router.push({ name: 'user', params: { userId: '123' }})
    
    // 带查询参数,变成 /register?plan=private
    router.push({ path: 'register', query: { plan: 'private' }})
    

    注意:如果提供了 path,params 会被忽略,上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name 或手写完整的带有参数的 path:

    const userId = '123'
    router.push({ name: 'user', params: { userId }}) // -> /user/123
    router.push({ path: `/user/${userId}` }) // -> /user/123
    // 这里的 params 不生效
    router.push({ path: '/user', params: { userId }}) // -> /user
    

    同样的规则也适用于 router-link 组件的 to 属性。
    在 2.2.0+,可选的在 router.push 或 router.replace 中提供 onComplete 和 onAbort 回调作为第二个和第三个参数。这些回调将会在导航成功完成 (在所有的异步钩子被解析之后) 或终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用。
    注意: 如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1 -> /users/2),你需要使用 beforeRouteUpdate 来响应这个变化 (比如抓取用户信息)。

    • router.replace(location, onComplete?, onAbort?)

      PS:<router-link :to="..." replace> 等同于router.replace(...)
      跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。

    • router.go(n)

    这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)

    // 在浏览器记录中前进一步,等同于 history.forward()
    router.go(1)
    
    // 后退一步记录,等同于 history.back()
    router.go(-1)
    
    // 前进 3 步记录
    router.go(3)
    
    // 如果 history 记录不够用,那就默默地失败呗
    router.go(-100)
    router.go(100)
    

    命名路由

    在前面的例子中你可能已经注意到了,比如这样

    router.push({ name: 'user', params: { userId: '123' }})
    

    或者

    <router-link :to="{ name: 'user', params: { userId: 123 }}"></router-link>
    

    这2种方法是等效的,这样就省去了path参数,

    命名视图

    【应用场景】

    比如你一个路由里的组件需要多个子组件布局,这几个子组件是公共复用的,那么通过命名路由视图的方式将大大降低你组件的耦合度和发杂性。
    看代码:

    <template>
        <!-- App.vue -->
        <div id="app">
            <router-view name="header"></router-view>
            <router-view name="body"></router-view>
        </div>
    </template>
    
    <style lang="less">
        #app {
            font-family: 'Avenir', Helvetica, Arial, sans-serif;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
            text-align: center;
            color: #2c3e50;
        }
    
        #nav {
            padding: 30px;
    
            a {
                font-weight: bold;
                color: #2c3e50;
    
                &.router-link-exact-active {
                    color: #42b983;
                }
            }
        }
    </style>
    
    
    //在脚手架里的router.js里配置路由
    import Vue from 'vue'
    import Router from 'vue-router'
    import Home from './views/Home.vue'
    import componentA from './components/componentA.vue'
    import componentB from './components/componentB.vue'
    
    Vue.use(Router)
    
    export default new Router({
        mode: 'history',
        base: process.env.BASE_URL,
        routes: [
            {
                path: '/',
                name: 'home',
                components: { //加个s
                    body: componentB,
                    header: componentA,
                }
            }
        ]
    })
    

    把刚才视图组件补齐

    <template>
        <!-- componentA.vue -->
        <div>
            <div class="header">
                header
            </div>
    
        </div>
    </template>
    <script>
        export default {
            name: 'componentA',
        }
    </script>
    <style lang="less" scoped>
        .header {
            background-color: lightgreen;
        }
    </style>
    
    <template>
        <!-- componentB.vue -->
        <div>
            <div class="bodyer">
                body
            </div>
        </div>
    </template>
    <script>
        export default {
            name: 'componentB',
        }
    </script>
    <style scoped lang="less">
        .bodyer {
            background-color: lightgrey;
        }
    </style>
    

    效果:


    image

    重定向和别名

    【应用场景】

    重定向的应用场景还是比较多的吧,经常用的就有默认路由重定向,别名就是给配置的路由名换个马甲,隐藏下真实的路由名吧,其他用处还没想到。
    重定向例子:
    重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b:

    const router = new VueRouter({
      routes: [
        { path: '/a', redirect: '/b' }
      ]
    })
    

    重定向的目标也可以是一个命名的路由:

    const router = new VueRouter({
      routes: [
        { path: '/a', redirect: { name: 'foo' }}
      ]
    })
    

    甚至是一个方法,动态返回重定向目标:

    const router = new VueRouter({
      routes: [
        { path: '/a', redirect: to => {
          // 方法接收 目标路由 作为参数
          // return 重定向的 字符串路径/路径对象
        }}
      ]
    })
    

    别名例子:
    /a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

    绝对别名
    const router = new VueRouter({
      routes: [
        { path: '/a', component: A, alias: '/b' }
      ]
    })
    
    默认别名
    const router = new VueRouter({
      routes: [
      { path: 'default', component: Default, alias: '' },
      ]
    })
    
    多个别名
    const router = new VueRouter({
      routes: [
      { path: 'baz', component: Baz, alias: ['/baz', 'baz-alias'] },
      ]
    })
    

    路由组件传参

    先看代码

    <template>
        <div>
            &nbsp;
            <router-link to="/b/c">c</router-link>// 这里我传了个参数“c”
        </div>
    </template>
    
    <script>
        // @ is an alias to /src
        export default {
        }
    </script>
    
    
    //在脚手架里的router.js里配置路由
    import Vue from 'vue'
    import Router from 'vue-router'
    import Home from './views/Home.vue'
    import componentA from './components/componentA.vue'
    import componentB from './components/componentB.vue'
    
    Vue.use(Router)
    
    const router = new Router({
        mode: 'history',
        base: process.env.BASE_URL,
        routes: [
            {
                path: '/',
                name: 'home',
                component: Home
            },       
            {
                path: '/b/:id',
                name: 'b',
                component: componentB,
                props: (route) => ({  //这里也可以通过props: true, 或者 props: {id : true}来传参 
                    id: route.params.id
                })
            }
        ]
    });
    export default router;
    
    <template>
        <!-- componentB.vue -->
        <div>
            <div class="bodyer">
                body: {{id}}
            </div>
        </div>
    </template>
    <script>
        export default {
            props: ['id'], //通过props接受参数
            name: 'componentB',
        }
    </script>
    <style scoped lang="less">
        .bodyer {
            background-color: lightgrey;
        }
    </style>
    

    HTML5 History 模式

    Vue-router是基于H5的history模式来实现的

    导航守卫

    记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

    全局前置守卫
    const router = new VueRouter({ ... })
    
    router.beforeEach((to, from, next) => {
      // ...
    })
    
    • to: 即将要进入的目标 路由对象

    • from: 当前导航正要离开的路由

    • next`:

    • next(false),表示终止跳转

    • next('需要跳转的目标路由')

    • next(可以传入router.push支持的任意内容')

    全局解析守卫

    router.beforeResolve和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

    全局后置钩子

    你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

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

    路由独享的守卫(beforeEnter)

    const router = new VueRouter({
      routes: [
        {
          path: '/foo',
          component: Foo,
          beforeEnter: (to, from, next) => {
            // ...
          }
        }
      ]
    })
    

    组件内的守卫

    • beforeRouteEnter
    • beforeRouteUpdate (2.2 新增)
    • beforeRouteLeave
    const Foo = {
      template: `...`,
      beforeRouteEnter (to, from, next) {
      next(vm => {
      console.log(vm)//vm就是组件实例,也就是this
      })
        // 在渲染该组件的对应路由被 confirm 前调用
        // 不!能!获取组件实例 `this`
        // 因为当守卫执行前,组件实例还没被创建
      },
      beforeRouteUpdate (to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用
        // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
        // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
        // 可以访问组件实例 `this`
      },
      beforeRouteLeave (to, from, next) {
        // 导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
      }
    }
    

    完整的解析流程

    完整的导航解析流程
    导航被触发。
    在失活的组件里调用离开守卫。beforeRouteleave
    调用全局的 beforeEach 守卫。
    在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
    在路由配置里调用 beforeEnter。
    解析异步路由组件。
    在被激活的组件里调用 beforeRouteEnter。
    调用全局的 beforeResolve 守卫 (2.5+)。
    导航被确认。
    调用全局的 afterEach 钩子。
    触发 DOM 更新。
    用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

    路由元信息

    这个东西特别适合权限检查,比如说登录。在路由配置里给每个路由做个标记,需要检查权限的标记下,那么不同权限就可以按照权限标准浏览页面了,岂不是美滋滋。
    看代码:

    <template>
        <!-- Home.vue -->
        <div>
            欢迎进入首页
        </div>
    </template>
    
    <script>
        // @ is an alias to /src
        export default {
    
        }
    </script>
    
    
    //在脚手架里的router.js里配置路由
    import Vue from 'vue'
    import Router from 'vue-router'
    import Home from './views/Home.vue'
    import login from './views/login.vue'
    import componentA from './components/componentA.vue'
    import componentB from './components/componentB.vue'
    
    Vue.use(Router)
    
    const router = new Router({
        mode: 'history',
        base: process.env.BASE_URL,
        routes: [
            {
                path: '/',
                name: 'home',
                component: Home,
                meta: {
                    isRequireLogin: true
                },
                beforeEnter(to, from, next) { //具体判断逻辑不写了
                    let judeLogin = () => {
                        let a = Math.random();
                        console.log(a)
                        if(a > 0.5) {
                            return false
                        }else {
                            return true
                        }
                    }
                    console.log(to.matched)
                    if(to.matched.some(record => record.meta.isRequireLogin)) {
                        if(judeLogin()) {
                            next()
                        } else {
                            next('/login')
                        }
                    }else {
                        next('/login')
                    }                
                }
            },       
            {
                path: '/login',
                name: 'login',
                component: login
            }   
        ]
    });
    export default router;
    
    <template>
        <!-- login.vue -->
        <div>
            登录页面
        </div>
    </template>
    
    <script>
        // @ is an alias to /src
        export default {
            methods: {
    
            }
        }
    </script>
    

    看效果:这里姑且当随机数大于0.5的时候,显示登录失败


    image

    数据获取

    有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:

    • 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。

    • 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

    个人觉得如果后端数据快的话用后面一种,快的话用前面那种。
    第一种不用说吧,直接在生命钩子create()里来实现,后面那种看代码:

      beforeRouteEnter (to, from, next) {
        //getPost是自定义的请求接口
        getPost(to.params.id, (err, post) => {
          next(vm => vm.setData(err, post))
        })
      },
    

    只要在beforeRouteEnter 这个路由守卫里实现就好了

    路由懒加载

    当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

    结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
    庆幸的是Vue/cli脚手架默认就是懒加载(webpack的方式),其他方式我暂时用不到,有兴趣的话可以看官网。

    【例子】
    import('./Foo.vue') // 返回 Promise
    

    【完】

    相关文章

      网友评论

          本文标题:Vue路由总结

          本文链接:https://www.haomeiwen.com/subject/txyopqtx.html