Vue路由总结

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

【完】

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容

  • 那这次呢?我决定直接就先放一个小小demo上来 其实我们在引入vue-router插件那一刻,我们的网页就已经附带...
    看物看雾阅读 796评论 0 1
  • SPA单页应用 传统的项目大多使用多页面结构,需要切换内容的时候我们往往会进行单个html文件的跳转,这个时候受网...
    视觉派Pie阅读 11,775评论 1 55
  • 一、vue-router实现原理 SPA(single page application):单一页面应用程序,只有...
    walycode阅读 1,039评论 1 3
  • 前言 vue-router是什么:是vue.js官方的路由管理器和vue.js的核心深度的集成,让开发者更加简单的...
    GUAN_one阅读 3,675评论 0 2
  • 一对夫妻,半月一小吵,一月一大吵,离婚也是常挂嘴边。 一次执行任务期间,男人打电话说:“吃饭没有?”女人说还没有。...
    妖墨子阅读 189评论 0 1