vue-router 试题
vue-router 的实现原理?
hash 模式 :
监听window.onhashchange事件,即监听url的hash值改变
window.addEventListener('hashchange', () => {
// this.transitionTo(...)
})
这是由于 vue-router 将自身作为一个插件安装到了 Vue,通过 Vue.mixin() 注册了一个 beforeCreate() 钩子函数,从而在之后所有的 Vue 组件创建时都会调用该钩子函数,给了检查是否有 router 参数,从而进行初始化的机会。进而通过层层调用执行了监听 hashchange 事件的动作。
hashchange 时,执行 history.transitionTo(...),在这个过程中,会进行地址匹配,得到一个对应当前地址的 route,然后将其设置到对应的 vm._route 上,vue-router 注入 Vue 的 beforeCreate 钩子函数中采用与 Vue 本身数据相同的“数据劫持”方式,这样对 vm._route 的赋值会被 Vue 拦截到,并且触发 Vue 组件的更新渲染流程。
vm._route 已经接收到路由的变更,从而触发视图更新。而当视图更新进一步调用到 <router-view> 的 render() 时,即进入了 <router-view> 的处理
可通过:<a href='#/123'>,或location.hash=‘’#123‘’来改变url 的hash值
详见:http://cnodejs.org/topic/58d680c903d476b42d34c72b
history 模式:
{ popState,pushState,replaceState }
history Api:https://developer.mozilla.org/zh-CN/docs/Web/API/History
使用流程:
// 0. 如果使用模块化机制编程,導入Vue和VueRouter,要调用 Vue.use(VueRouter)
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 1. 定义(路由)组件。
// 可以从其他文件 import 进来
const Foo = {
template:
`<div>foo
<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>
</div>`
}
const Bar = { template: '<div>bar</div>',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
}
}
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2> // 动态路由使用 this.$route.params 取参
<router-link to="/index" tag="li" :class="{active: this.$store.state.tabIndex === 0}">首页</router-link>
<router-link to=`/detail/${this.id}`>详情</router-link>
<router-link :to="{ name: 'user', params: { id: 123 }}">User</router-link>//{ path: `/user/${userId}` }
<route-link :to="{path:/search',query:{name:123}" exact>search</router-link>
<router-view></router-view>
</div>
`
}
const Foo = {
template: `...`,
keepAlive: true,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当钩子执行前,组件实例还没被创建
if(!isLogin){
next({path: '/login',query: { redirect: to.fullPath }})// 没有登录,重定向到登录页面
}else{ next()} //next(false),表示不跳转
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是改组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
// 2. 定义路由,每个路由应该映射一个组件。 其中"component" 可以是通过 Vue.extend() 创建的组件构造器,或者,只是一个组件配置对象。
const routes = [
{
path: '/',
components: { // 命名视图
default: Foo,
a: Bar,
b: Baz
},
beforeEnter: (to, from, next) => { // ... }
}
{ path: '/a', redirect: { name: 'foo' }} // 重定向
{ path: '/bar/:id(\d{8})', component: Bar }, // 可以在路由配置中通过正则表达式对参数进行校验
// 动态路径参数 以冒号开头,以 / 开头的嵌套路径会被当作根路径
{ path: '/user/:id', component: User,name: 'user',
children: [
// 当 /user/:id 匹配成功, UserHome 会被渲染在 User 的 <router-view> 中
{ path: '', component: UserHome },
// 当 /user/:id/profile 匹配成功,UserProfile 会被渲染在 User 的 <router-view> 中
{
path: 'profile',
component: UserProfile
}
]
}
]
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写)相当于 routes: routes
})
router.beforeEach((to, from, next) => { // ... })
router.afterEach((to, from) => { // ... })
// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')
// 现在,应用已经启动了!
动态路由
你可以在一个路由中设置多段『路径参数』,对应的值都会设置到 $route.params 中。例如:
| 模式 | 匹配路径 | $route.params |
|---|---|---|
| /user/:username | /user/evan | { username: 'evan' } |
| /user/:username/post/:post_id | /user/evan/post/123 | { username: 'evan', post_id: 123 } |
除了 $route.params 外,$route 对象还提供了其它有用的信息,例如,$route.query(如果 URL 中有查询参数)、$route.hash 等等。
路由守卫应用场景:如:用户有信息未保存,用户是否有权限跳转页面,用户是否登录等
编程式导航
router.push('home') // 字符串
router.push({ path: 'home' }) // 对象
router.push({ name: 'user', params: { userId: 123 }})// 命名的路由,/user/123
router.push({ path: 'register', query: { plan: 'private' }})// 带查询参数,变成 /register?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?)
router.go(n) 这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。
命名路由
有时候,通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。
const router = new VueRouter({ routes: [ { path: '/user/:userId', name: 'user', component: User } ] })
要链接到一个命名路由,可以给 router-link 的 to 属性传一个对象:
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
这跟代码调用 router.push() 是一回事:
router.push({ name: 'user', params: { userId: 123 }})
这两种方式都会把路由导航到 /user/123 路径。
vue-router可以在路由配置中对参数进行校验
path: "/detail/:id(\d{8})"
全局守卫
你可以使用 router.beforeEach 注册一个全局前置守卫:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
每个守卫方法接收三个参数:
-
to: Route: 即将要进入的目标 路由对象 -
from: Route: 当前导航正要离开的路由 -
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖next方法的调用参数。-
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。 -
next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from路由对应的地址。 -
next('/')或者next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。 -
next(error): (2.4.0+) 如果传入next的参数是一个Error实例,则导航会被终止且该错误会被传递给router.onError()注册过的回调。
-
确保要调用 next 方法,否则钩子就不会被 resolved。
全局后置钩子
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:
router.afterEach((to, from) => {
// ...
})
路由独享的守卫
你可以在路由配置上直接定义 beforeEnter 守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})

浙公网安备 33010602011771号