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 }
]
})
在搭建页面框架时需要注意到这点,复用
pageRouter
和pageHeader
在一个路由中设置多段“路径参数”,对应的值都会设置到 $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 的回调函数。
图示为首次渲染进入页面的调用顺序。
如何获取路由传过来的参数
- 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.history
与 window.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(跨域资源共享),可能会限制实际加载新页面。会立即触发 popstate
或 hashchange
监听。
window.location.href
、window.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,pushState
和 replaceState
,我们可以使用它很方便的达到改变url不重载页面的目的。