Vue路由-动态路由-编程式导航

  通常我们会用一个或多个路由表,来匹配所有页面的路径。但这不能满足一些特定的需求。

# 解惑

  好几次被问到怎么区分$route$router,这里统一给出解释:

  • this.$router:指的是router实例,也就是new VueRouter()的执行结果,使用效果与router相同。有这种写法是因为Vue并不想在每个独立需要封装路由的组件中都导入路由,因此在全局创建了该对象。
  • this.$route:指的是当前 激活路由信息对象,又称路由记录,该对象是只读的,内部属性不可改变,当路由发生重定向或路由参数改变时,该对象会被重新计算。一般我们会通过watch来监听它们。

# 基本路由

  对于基本路由,我们需要精准的匹配路由内容,才能实现页面跳转。其编写风格大致如下

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import 404 from '@/404'
Vue.use(Router)

const router =  new Router({
    routes: [
        { path: '/', redirect: '/index' },
        { path: '/index', component: HelloWorld, alias: '/home' },
        { path: '/404', meta: { title: '错误页面' }, component: 404 },
        { path: '/b', redirect: to => {
          // 接收目标路由to作为参数,return值作为动态响应的路径
        } }
    ]
})

export default router

  在这个案例中,this.$router就是router常量,而this.$route是在实例化后,即将登场的那个路由对象,如假设为{ path: '/index',component: HelloWorld }
meta 为路由元信息,常提供给 导航守卫 和this.$route 使用
alias是路由别名,其作用是当匹配到/home时,访问的是/home路由但真正走的是/index路由的页面

import为何能实现路由懒加载

  曾经我也困惑过作为编译时调用的import是如何实现懒加载的。其实对比
import('./index.vue')(编译时加载)和
const Index = () => import('./index.vue')(懒加载)
简单理解:后者编译时只是声明了函数,只有使用时才运行该内容。
深入一些:利用Promise 可以将异步组件定义为返回一个Promise 的工厂函数,又因为webpack2中动态import能实现代码分块,从而达到定义一个能被Webpack自动代码分割的异步组件的效果

# 动态路由匹配

  例如,我们有一个 user 组件,对于所有ID各不相同的用户,都要使用这个组件来渲染,因为用户ID未知,不可能再路由表里书写这个路由,这时,就需要使用到 vue-router 中的 “动态路径参数” 来达到这个效果

const router = new VueRouter({
  routes: [
    // 动态路径参数,以冒号开头
    { path: '/user/:id', component: () => import('@/components/User') }
  ]
})

  现在呢,对于如/user/12345/user/e9e0jh830d 类型的路由,都会被映射到相同的路由路径上,而 /user/ 后的则是本路径的参数,页面初始化时并不关心它是什么内容,认为这是提供给页面内部使用的一个参数

  既然是参数,页面就应该有能力获取,通常我们需要根据这个参数发起请求获取页面更详细的渲染数据,获取参数的办法如下

  • 单个参数
    Path:{ path: '/user/:id' }
    Url:/user/e9e0jh830d
const para = this.$route.params 
// { id: e9e0jh830d}
  • 支持多个参数
    Path: { path: 'user/:username/post/:post_id' }
    Url: /user/zhangfs/post/330473
const para = this.$route.params 
// { username: zhangfs, post_id: '330473' }

# 响应路由参数的变化!!!

  有个很经典的问题:假设在一个 User 列表中,用户切换查看不同账户信息,Url上/user/:id后面的 id 发生了变化实现了用户切换,然而页面本该展示不同用户信息的数据并没有更新。
  原因是Vue在设计初衷,为了最大化高效利用组件,并不会销毁后再创建,也就是说,组件的生命钩子如Created将不会再次被触发调用,这就要求我们自己实现触发。笔者提供4种思路
(1)监听路由参数,发生变化则重新获取页面渲染数据
(2)监听路由(Vue推荐)

watch: {
  '$route' (to, from) { ... } // to-from 都能监听出变化做出响应
}

(3)使用组件内导航守卫 beforeRouteUpdate

beforeRouteUpdate (to, from, next) { ... } // 别忘记调用next()

(4)为<router-view>增加唯一的key。这种方式是逆Vue思路来的,设计Key关键字实现销毁组件重新挂载来保证生命钩子重新触发

<router-view :key="key" />

computed: {
  key() {
    return this.$route.name ? this.$route.name + new Date() : this.$route + new Date()
  }
}

# 嵌套的子路由

  通常,在app中声明的路由为 顶级路由,作为顶层的出口,渲染最高级路由匹配到的组件。

<div id="app">
  <router-view></router-view>
</div>
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }
  ]
})

  如果需要实现子路由,如匹配 /user/:id/posts,则需要设置嵌套的路由,使用children属性定义

const router = new VueRouter({
  routes: [
    { 
      path: '/user/:id',
      component: User,
      children: [
        {
          // 当 /user/:id/profile 匹配成功,
          // UserProfile 会被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 当 /user/:id/posts 匹配成功
          // UserPosts 会被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

注意】以/开头的路径会被当做根路径。也就是说允许你充分的使用嵌套组件而无需设置嵌套的路径

  该例中,当遇到访问/user的路径不会渲染任何东西,是因为没有匹配到任何合适的子路由。当然你可以提供一个空的子路由来承接该路径

children: [
  { path: '', component: UserHome }
]

# 编程式导航

  我们知道<router-link>实际上是创建了a标签来实现导航链接,我们还可以借助 router 的实例方法通过编写代码来实现。注意,如在开篇描述,组件内部应该使用this.$router访问

# this.$router.push()
声明式 编程式
<router-link :to="..." /> this.$router.push(...)

使用语法: this.$router.push(location, onComplete回调. onAbort回调)
location接受一个字符串路径,或一个描述地址的对象

this.$router.push('dashboard') // -> /dashboard
this.$router.push({ path: 'dashboard' }) // -> /dashboard
this.$router.push({ name: 'user', parmas: { userId: '123' } }) // -> /user/123
this.$router.push({ path: 'register', query: { plan: 'private' } }) // -> /register?plan=private

】注意观察第三个和第四个,重定向带路径参数的路由时,不能指定 path,因为提供了 pathparams参数会自动被忽略。如果想要用path则需要修改如下:

const userId = '123'
this.$router.push({ path: `/user/${userId}` }) // -> /user/123
// 错误示例
this.$router.push({ path: `/user`, params: { userId: '123' } }) // -> /user

  同样的规则适用于 <router-link :to="">to 属性

# this.$router.replace()
声明式 编程式
<router-link :to="..." replace/> this.$router.replace(...)

  跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。

# this.$router.go(n)

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

this.$router.go(1) // 前进一步
this.$router.go(-1) // 后退一步
this.$router.go(-100) // 如果记录不够用,默默失败不阻塞

# 常用路由对象属性

  • 当前路由 - $route.path
    Path: { path: '/user/:id' }
    Url: user/12345
    说明:总是解析为绝对路径,无视是否为路径参数。返回对象
const path = this.$route.path
// user/12345
  • url参数 - $route.query
    Path: { path: '/user' }
    Url: /user?id=e9e0jh830d
const query = this.$route.query
// { id: e9e0jh830d }
  • 路由参数- $route.params
    Path: { path: '/user/:id' }
    Url: /user/12345
const para = this.$route.params
// { id: 12345 }

# 常用路由导航守卫

  • 全局前置守卫 - router.beforeEach

  这是最常使用也是最重要的守卫之一,使用时要调用 next() 方法表示导航 resolved;因为守卫是异步解析执行,没有resolve的导航守卫处在等待中阻断执行进程,路由也不会实现跳转

next()允许接收一个参数:(1)false - 中断当前导航,(2){ path: '/route_name' } - 实现跳转

router.beforeEach((to, from, next) => {
  // `to` 和 `from`都是路由对象
})
  • 全局后置钩子 - router.afterEach

  与前置导航守卫不同的是,afterEach并不是“守卫”,而是“钩子”,它不会阻塞线程,也不需要调用next()来表达resolve状态。可以认为是线程流水线里的一个作业而已

router.afterEach((to, from) => { ... })
  • 私有路由守卫 - router.beforeEnter

  与beforeEachafterEach不同的是,beforeEnter是针对单个路由进行配置的导航守卫,作用于所在路由内而不影响其他路由路径。该守卫也需要调用next()来表示守卫结束状态

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


  另外还有 组件内的守卫 支持在组件内直接定义导航守卫。需注意的是,其中的beforeRouterEnter守卫不能访问this对象,理由是守卫在导航确认前即将登场的新组件并未被创建,需要通过 next(vm) 中的 vm 参数来调用组件实例

export default {
  data () {
    ...
  },
  beforeRouteEnter (to, from, next) {
    getPost(to.params.id, (err, post) => {
      next(vm => vm.setData(err, post))
    })
  },
  methods: { ... }
}
  • 完整的导航解析流程
  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数

# 路由过渡动效

<router-view> 是基本的动态组件,可以用 <transition> 组件增加过渡效果

<transition>
  <router-view></router-view>
</transition>

  以上方式会给所有的路由增加相同的简单的切换过渡效果,如果想个性化给每个路由增加,则需要在组件内使用 <transition> 并设置不同的 name实现页面级的过渡效果

<transition name="fade" mode="out-in">
  <router-view/> <!-- 也可以直接写在某个具体的组件内<template>...</template> -->
</transition>
.fade-enter {
  opacity:0;
}
.fade-leave{
  opacity:1;
}
.fade-enter-active{
  transition:opacity .5s;
}
.fade-leave-active{
  opacity:0;
  transition:opacity .5s;
}

# 路由切换滚动

  当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。该功能需要浏览器支持history.pushBehaviorAPI

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition // 滚动到上一个位置
    } else {
      return { x: 0, y: 0 } // 滚动到顶部
    }
  }
})

  或者可以模拟滚动到某个锚点

scrollBehavior (to, from, savedPosition) {
  if (to.hash) {
    return {
      selector: to.hash
    }
  }
}

  甚至可以配合路由元信息meta实现更细颗粒度控制滚动,和利用返回一个Promise来得出预期的位置滚动。

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

推荐阅读更多精彩内容