Vue Router(Vue路由详解)

学习至官网 https://router.vuejs.org/zh/

模块化工程使用

在入口文件main.js中引用router:

import Vue from 'vue'
import App from './App'
import router from './router'
......
// 通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
new Vue({
  el: '#app',
  router,
  ......
  render: h => h(App)
})

在同级router文件夹下新建index.js文件初始化router实例:

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)
// 定义路由
// 每个路由应该映射一个组件
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]
// 创建 router 实例,然后传 `routes` 配置
const router = new Router({
  routes,
  mode: 'history'
})
export default router

导航

声明式导航

使用 router-link 组件来导航

<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<!--<router-link> 对应的路由匹配成功,将自动设置 class 属性值.router-link-active -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>

<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>

编程式导航

借助 router 的实例方法,通过编写代码来实现导航。

router.push

使用this.$router.push 方法导航到不同的 URL。这个方法会向 history 栈添加一个新的记录,当用户点击浏览器后退按钮时,则回到之前的 URL。

// 点击 <router-link :to="..."> 等同于调用 router.push(...)
this.$router.push(location, onComplete?, onAbort?)

onComplete:回调将会在导航成功完成 (在所有的异步钩子被解析之后)的时候进行相应的调用
onAbort:回调将会在终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用
// 参数形式
// 1.字符串
router.push('home')

// 2.对象
router.push({ path: 'home' })

// 3.命名的路由
router.push({ name: 'user', params: { userId: 123 }})

// 4.带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

// 提供了 path,params 会被忽略,如下:
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 属性。

router.replace

使用this.$router.replace 方法与router.push相同。唯一的区别是这个方法不会向 history 栈添加一个新的记录。

router.replace(location, onComplete?, onAbort?)
// 声明式
<router-link :to="..." replace>
router.go

参数是一个整数,意思是在 history 记录中向前或者后退多少步。

这些router接口都是效仿 window.history API 的

$router 与 $route

通过注入路由器,我们可以在任何组件内通过 this. $router 访问路由器,也可以通过this. $route访问当前路由。

动态路由匹配

可以使用『动态路径参数』把某种模式匹配到的所有路由都映射到同一个组件。

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

routes: [
    // 动态路径参数 以冒号: 开头
    // 当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用
    { path: '/user/:id', component: User }
]

可以设置多段"路径参数"。

/user/:username/post/:post_id

当使用路由参数时,两个路由渲染同个组件,原来的组件实例会被复用。这也意味着组件的生命周期钩子不会再被调用。

响应路由参数变化

想要对路由参数的变化做出响应,可以有以下两种方式:

// 第一种 用 watch 来检测 $route
const User = {
  template: '...',
  watch: {
    '$route' (to, from) {
      // 对路由变化作出响应...
    }
  }
}

// 第二种 引入 beforeRouteUpdate 导航守卫
const User = {
  template: '...',
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
  }
}

高阶的匹配模式

// 下面列出几种情况
routes: [
    { path: '/' },
    // params are denoted with a colon ":"
    { path: '/params/:foo/:bar' },
    // a param can be made optional by adding "?"
    { path: '/optional-params/:foo?' },
    // a param can be followed by a regex pattern in parens
    // this route will only be matched if :id is all numbers
    { path: '/params-with-regex/:id(\\d+)' },
    // asterisk can match anything
    { path: '/asterisk/*' },
    // make part of th path optional by wrapping with parens and add "?"
    { path: '/optional-group/(foo/)?bar' }
]

匹配优先级

有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。

路由/视图

嵌套的路由 children

App.vue中的 router-view 是最顶层的出口

<div id="app">
  <router-view></router-view>
</div>

一个被渲染的组件也可以包含自己嵌套的 router-view

// 在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置
// 嵌套路径也可以以'/'开头,但是会被当做根路径,与parent的路径不会连接
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
      	// 提供一个空的子路由
      	// 当 /user/:id 匹配成功,
        // UserHome 会被渲染在 User 的 <router-view> 中
        { path: '', component: UserHome },
        {
          // 当 /user/:id/profile 匹配成功,
          // UserProfile 会被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 当 /user/:id/posts 匹配成功
          // UserPosts 会被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

命名路由 name

在routes中给某个路由设置名称。

routes: [
    {
      path: '/user/:userId',
      name: 'user',
      component: User
    }
  ]
// 链接一个命名路由
// 声明式
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
// 编程式
router.push({ name: 'user', params: { userId: 123 }})

命名视图

想同级展示多个视图,而不是嵌套展示的情况。

// 给router-view设置名称,默认为default
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>

// 一个视图需要一个组件渲染,多个视图就要在components中配置多个组件
routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]

嵌套的命名视图

嵌套的路由与多个命名视图的结合使用,即在children中的路由对象设置components。

路由重定向与别名

重定向

通过路由对象的redirect属性进行重定向。

// redirect 可以是如下的方法动态返回
// 或者直接使用 字符串路径/路径对象
routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
    }}
  ]

导航守卫不会应用到跳转路由上,而是redirect目标上。

别名

通过路由对象的alias属性定义别名。

// 用户访问/b时,url为/b,但路由匹配到的是/a
// 别名使展示的UI结构不受限于配置的嵌套路由结构
const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})

路由组件传参

用组件中的props取代$route.params,使组件与路由解耦

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User, props: true },

    // 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
    {
      path: '/user/:id',
      components: { default: User, sidebar: Sidebar },
      props: { default: true, sidebar: false }
    }
  ]
})
布尔模式

如果 props 被设置为 true,route.params 将会被设置为组件属性。

对象模式

如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用。

函数模式

你可以创建一个函数返回 props。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。

const router = new VueRouter({
  routes: [
    { path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
  ]
})

路由元信息 meta

一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。

meta的使用

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // this route requires auth, check if logged in
    // if not, redirect to login page.
    if (!auth.loggedIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next() // 确保一定要调用 next()
  }
})

vue-router模式 mode

hash模式

默认是hash模式,使用URL的hash来模拟完整的 URL, URL改变时页面不会重新加载。

history模式

history.pushState API 来完成 URL 跳转而无须重新加载页面。

服务器端需要增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。否则会返回404。

// nginx配置
location / {
  try_files $uri $uri/ /index.html;
}

这样一来,服务器对于所有的路径都不会返回404,统一返回index.html,需要在Vue路由当中配置找不到路由时返回404页面。

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '*', component: NotFoundComponent }
  ]
})

导航守卫

导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。

全局守卫

全局守卫的3种方式
router.beforeEach

全局前置守卫:导航触发,进入路由之前。

router.beforeResolve

全局解析守卫:在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

router.afterEach

全局后置钩子:钩子不会接受 next 函数也不会改变导航本身。

import router from './router'; // 引入路由
router.beforeEach((to, from, next) => { 
   next();
});
router.beforeResolve((to, from, next) => {
   next();
});
router.afterEach((to, from) => {
   console.log('afterEach 全局后置钩子');
});

路由独享的守卫

在路由配置上直接定义 beforeEnter 守卫

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

组件内的守卫

  1. beforeRouteEnter 进入路由前
  2. beforeRouteUpdate (2.2 新增) 路由复制同一个组件(参数或查询的改变)
  3. beforeRouteLeave 离开路由前
const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
    // 可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数
    next(vm => {
    	// 通过 `vm` 访问组件实例
  	})
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
    // 离开守卫通常用来禁止用户在还未保存修改前突然离开,可以通过 next(false) 来取消
    const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  	if (answer) {
    	next()
  	} else {
    	next(false)
  	}
  }
}

守卫参数

to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
  1. next(): 进行管道中的下一个钩子。
  2. next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
  3. next(’/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
  4. next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

确保要调用 next 方法,否则钩子就不会被 resolved。

完整的导航解析流程

  1. 导航被触发进入其他路由。
  2. 调用要离开路由的组件守卫beforeRouteLeave。
  3. 调用全局前置守卫:beforeEach。
  4. 在重用的组件里调用 beforeRouteUpdate。
  5. 调用路由独享守卫 beforeEnter。
  6. 解析异步路由组件。
  7. 在将要进入的路由组件中调用beforeRouteEnter。
  8. 调用全局解析守卫 beforeResolve
  9. 导航被确认。
  10. 调用全局后置钩子的 afterEach 钩子。
  11. 触发DOM更新(mounted)。
  12. 执行beforeRouteEnter 守卫中传给 next 的回调函数。

路由的数据获取

有两种获取数据的方式
导航完成之后获取

在组件本身的生命周期钩子中去获取数据

  created () {
    // 组件创建完后获取数据,
    // 此时 data 已经被 observed 了
    this.fetchData()
  },
  watch: {
    // 如果路由有变化,会再次执行该方法
    '$route': 'fetchData'
  },
导航完成之前获取

在路由进入的守卫中获取数据,获取数据成功后再进行导航

  beforeRouteEnter (to, from, next) {
    getPost(to.params.id, (err, post) => {
      next(vm => vm.setData(err, post))
    })
  },
  // 路由改变前,组件就已经渲染完了
  // 逻辑稍稍不同
  beforeRouteUpdate (to, from, next) {
    this.post = null
    getPost(to.params.id, (err, post) => {
      this.setData(err, post)
      next()
    })
  },

路由切换时的界面滚动行为

创建Router实例时,提供一个scrollBehavior方法。

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
  	 // savedPosition:通过浏览器的前进/后退按钮时触发
    // return 期望滚动到哪个的位置(两种方式)
    // return { x: number, y: number }
    // return { selector: string, offset? : { x: number, y: number }}
    // return falsy值(布尔类型可以强制返回的「假」值)或空对象,不发生滚动
  }
})

滚动方式

滚回到页面顶部
scrollBehavior (to, from, savedPosition) {
  return { x: 0, y: 0 }
}
前进后退时模拟浏览器的原生表现
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
    }
  }
}
异步滚动
scrollBehavior (to, from, savedPosition) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ x: 0, y: 0 })
    }, 500)
  })
}

路由懒加载

将不同路由对应的组件分割成不同的代码块,当路由被访问的时候才加载对应组件。

// 定义一个能够被 Webpack 自动代码分割的异步组件
const Foo = () => import('./Foo.vue')
// 在路由中正常使用
const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})

// 可以使用命名chunk来定义打包代码块的名称,而不是用id
// Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值