vue系列-vue-router

Vue-Router官网:https://router.vuejs.org/zh/guide/

基础

vue-router是什么

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

安装

模块化方式安装。

vue-router 依赖包安装 npm install vue-router 后,需要在vue实例上进行挂载。

// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)

// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
  routes // (缩写) 相当于 routes: routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
  router
}).$mount('#app')

// 现在,应用已经启动了!

动态路由匹配

当使用路由参数时,例如从 /user/foo 导航到 /user/bar原来的组件实例会被复用所以组件的生命周期钩子不会再被调用

复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象:

const User = {
  template: '<div>User</div>'
}

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

在搭建页面框架时需要注意到这点,复用 pageRouterpageHeader

在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params 中。

路径匹配

常规参数只会匹配被 / 分隔的 URL 片段中的字符。如果想匹配任意路径,我们可以使用通配符 (*)。

匹配路径是循环遍历,所以如果前面没有匹配到的路由可以匹配最后的通配符。

当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: '*' } 通常用于客户端 404 错误。history模式下,需要后端配合或者在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。

注意

/ 开头的嵌套路径会被当作根路径。

参数或查询的改变并不会触发进入/离开的导航守卫

确保 next 函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错

router-link组件

<router-link> 组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a> 标签,可以通过配置 tag 属性生成别的标签.。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。

<router-link> 比起写死的 <a href="..."> 会好一些,理由如下:

  • 无论是 HTML5 history 模式还是 hash 模式,它的表现行为一致,所以,当你要切换路由模式,或者在 IE9 降级使用 hash 模式,无须作任何变动。
  • 在 HTML5 history 模式下,router-link 会守卫点击事件,让浏览器不再重新加载页面。
  • 当你在 HTML5 history 模式下使用 base 选项之后,所有的 to 属性都不需要写 (基路径) 了。
  • a 标签会造成页面的强制刷新,需要手动阻止默认行为。

属性值 props:

  • to: 表示目标路由的链接。
  • replace:切换路由时使用router.replace
  • append:在当前(相对)路径前添加基路径。
  • tag:指定 router-link 渲染成其他标签,默认为 a 标签。
  • active-class:设置连接激活时使用的 css 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。
  • exact:启用“精确匹配模式”。“是否激活”默认类名的依据是包含匹配。
  • event:可以用来触发导航的事件。
  • exact-active-class:当连接被精确匹配时应激活的 class 样式。默认值可以通过路由构造函数选项 linkExactActiveClass 进行全局配置。
  • aria-current-value:当链接根据精确匹配规则激活时配置的 aria-current 的值。这个值应该是 ARIA 规范中允许的 aria-current 的值 (opens new window)。在绝大多数场景下,默认值 page 应该是最合适的。

router-view组件

<router-view> 组件是一个 functional 组件,渲染路径匹配到的视图组件。<router-view> 渲染的组件还可以内嵌自己的 <router-view>,根据嵌套路径,渲染嵌套组件。

其他属性 (非 router-view 使用的属性) 都直接传给渲染的组件, 很多时候,每个路由的数据都是包含在路由参数中。

因为它也是个组件,所以可以配合 <transition><keep-alive> 使用。如果两个结合一起用,要确保在内层使用 <keep-alive>

<transition>
  <keep-alive>
    <router-view></router-view>
  </keep-alive>
</transition>

设置命名视图,可以在同一个组件中使用多个 router-view 组件。

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s):

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})

route 是响应式的,路由发生了变化会改变 app.route 响应 route 的 setter 导致 router-view 组件的 render 函数。

源码

初始化

Vue-Router 的 install 方法会给每一个组件注入 beforeCreated 和 destroyed 钩子函数,在 beforeCreated 做一些私有属性定义和路由初始化工作。

路由初始化的时机是在组件的初始化阶段,执行到 beforeCreate 钩子函数的时候会执行 router.init 方法。然后又会执行 history.transitionTo 方法做路由过渡

matcher

createMatcher 的初始化就是根据路由的配置描述创建映射表,包括路径、名称到路由 record 的映射关系。

match 会根据传入的位置和路径计算出新的位置,并匹配到对应的路由record,然后根据新的位置和 record 创建新的路径并返回。

路径切换

路由始终会维护当前的线路,路由切换的时候会把当前线路切换到目标线路,切换过程中会执行一系列的导航守卫钩子函数,会更改url,同样也会渲染对应的组件,切换完毕后会把目标线路更新替换当前线路,这样就会作为下一次的路径切换的依据。

面试题

**参考:Vue-Router面试题汇总 - 掘金

路由有几种模式,区别

  • hash模式:使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器。例http://www.abc.com/#/index,hash值为#/index, hash的改变会触发hashchange事件,通过监听hashchange事件来完成操作实现前端路由。hash值变化不会让浏览器向服务器请求。主要使用 window.location 方式进行页面跳转,如window.location.assign()window.location.replace()
  • history模式:依赖 HTML5 History API 和服务器配置。没有#,路由地址跟正常的url一样,但是初次访问或者刷新都会向服务器请求,如果没有请求到对应的资源就会返回404,所以路由地址匹配不到任何静态资源,则应该返回同一个index.html 页面,需要在nginx中配置。页面路径操作为history api的方式,如history.pushState()history.replaceState()
  • abstract模式:支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。

完整的导航守卫流程

  • 导航被触发。
  • 如果不是首次加载页面,在失活的组件里调用组件内离开钩子beforeRouteLeave(to,from,next)
  • 调用全局前置守卫beforeEach((to,from,next) => {} ),可以改变导航本身。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中
  • 在重用的组件里调用组件内的更新守卫 beforeRouteUpdate(to,from,next)
  • 在路由配置里调用路由独享的守卫beforeEnter(to,from,next),在配置路由时定义。
  • 解析异步路由组件。
  • 在被激活的组件里调用组件内的前置钩子 beforeRouteEnter(to,from,next),此时不能访问 this ,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建,可以设置 next 回调来访问组件实例。
  • 在所有组件内守卫和异步路由组件被解析之后调用全局解析守 beforeResolve((to,from,next) => {} )
  • 导航被确认。
  • 调用全局后置钩子afterEach((to,from) => {} ),不会改变导航本身。
  • 触发 DOM 更新,执行 vue 组件的生命周期,组件创建挂载后。
  • 用创建好的实例调用组件内的前置钩子 beforeRouteEnter 中传给 next 的回调函数。

图示为首次渲染进入页面的调用顺序。

vur-router守卫

如何获取路由传过来的参数

  • router配置参数meta:如this.$route.meta.title
  • url上携带的参数query:使用 path 和 query 配合传参。如浏览器地址:http://localhost:8036/home?userId=123 获取方式:this.$route.query.userId
  • params传参:使用 name 和 params 配合传参。router.push({ name: 'user', params: { userId: '123' }}) 获取参数:this.$route.params.userId
  • 在配置router时,设置props属性,可以在组件的props中获取:props方式

路由之间的跳转

  • 声明式:通过使用内置组件<router-link :to="/home">来跳转
  • 编程式:通过调用router实例的push方法router.push({ path: '/home' })或 replace 方法router.replace({ path: '/home' })

Route与Router的关系

  • route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
  • router是“路由实例对象”,包括了路由的跳转方法,钩子函数等。

Vue路由如何打开新窗口

const obj = {
    path: xxx,//路由地址
    query: {
       mid: data.id//可以带参数
    }
};
const {href} = this.$router.resolve(obj);
window.open(href, '_blank');

Vue-Router 切换页面 与 window.location 的区别

要区分路由导航的方式。

router导航方式 hash模式 history模式
router.replace(…) window.location.replace(…) history.replaceState(…)
router.push(…) window.location.hash(…) history.pushState(…)
router.go(n) window.history.go(n) window.history.go(n)
事件监听 hashchange popstate

在支持window.history的情况下,hash模式也是使用history模式的api操作,如果history.pushState失败会使用window.location.assign(…)处理异常。回退事件会触发 popstate 事件监听。

不支持 window.history 的情况下,hash模式则会使用 window.location 方式进行操作 url,回退事件会触发 hashchange 事件监听。

本质上就是问 window.historywindow.location 的区别。

history.pushState(…):调用 pushState() 后浏览器并不会立即加载这个URL,但可能会在稍后某些情况下加载这个URL,比如在用户重新打开浏览器时,后退时。新URL不必须为绝对路径。如果新URL是相对路径,那么它将被作为相对于当前URL处理。新URL必须与当前URL同源,否则 pushState() 会抛出一个异常。该参数是可选的,缺省为当前URL。

window.onpopstate = function(e) {
   alert(2);
}

let stateObj = {
    foo: "bar",
};

history.pushState(stateObj, "page 2", "bar.html"); // 假如现在用户在bar.html点击了返回按钮,将会执行alert(2)。

window.location:只要赋给 location 对象一个新值,文档就会使用新的 URL 加载,就好像使用修改后的 URL 调用了 window.location.assign() 一样。需要注意的是,安全设置,如 CORS(跨域资源共享),可能会限制实际加载新页面。会立即触发 popstatehashchange监听

window.location.hrefwindow.location.hash 的操作结果和window.location一致。

在某种意义上,调用 pushState() 与 设置 window.location = "#foo" 类似,二者都会在当前页面创建并激活新的历史记录。但 pushState() 具有如下几条优点:

  • 新的 URL 可以是与当前URL同源的任意URL 。相反,只有在修改哈希时,设置 window.location 才能是同一个 document
  • 如果你不想改URL,就不用改。相反,设置 window.location = "#foo";在当前哈希不是 #foo 时, 才能创建新的历史记录项。
  • 你可以将任意数据和新的历史记录项相关联。而基于哈希的方式,要把所有相关数据编码为短字符串。
  • 如果 title参数,随后还会被浏览器所用到,那么这个数据是可以被使用的(哈希则不是)。

注意 pushState() 绝对不会触发 hashchange 事件,即使新的URL与旧的URL仅哈希不同也是如此。

调用history.pushState()或者history.replaceState()不会触发popstate事件. popstate事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮(或者在JavaScript中调用history.back()、history.forward()、history.go()方法),此外,a 标签的锚点也会触发该事件.

如果vue项目是单页面项目,其实router操作只是组件的显隐,所以不是真正的页面跳转。如果使用页面跳转会耗费更多的性能去加载渲染页面。

vue-router 跳转原理类似于 pjax (pushState + ajax) 都使用了history state API。

关于 history.pushState :

更多阅读:https://blog.csdn.net/helloxiaoliang/article/details/73850428

在浏览器中改变地址栏url,将会触发页面资源的重新加载,这使得我们可以在不同的页面间进行跳转,得以浏览不同的内容。但随着单页应用的增多,越来越多的网站采用ajax来加载资源。因为异步加载的特性,地址栏上的资源路径没有被改变,随之而来的问题就是页面的状态无法被保存。这导致我们难以通过熟悉的方式(点击浏览器前进/后退按钮),在前后的页面状态间进行切换。

为了解决ajax页面状态不能返回的问题,人们想出了一些曲线救国的方法,比如利用浏览器hash的特性,将新的资源路径伪装成锚点,通过onhashchange事件来改变状态,同时又避免了浏览器刷新。但这样始终显得有些hack。

现在HTML5规范为 window.history引入了两个新api,pushStatereplaceState,我们可以使用它很方便的达到改变url不重载页面的目的。