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

注意:如果提供了 pathparams 会被忽略,上述例子中的 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) => {
        // ...
      }
    }
  ]
})

 

posted @ 2017-12-06 18:16  fanlinqiang  阅读(345)  评论(0)    收藏  举报