美文网首页
前端笔记 — VueRouter

前端笔记 — VueRouter

作者: codingZero | 来源:发表于2020-06-13 15:56 被阅读0次

一. 路由的简单使用

<div id='app'>
     <p>
          <!-- 使用 router-link 组件来导航,通过传入 to 属性指定链接 -->
          <!-- <router-link> 默认会被渲染成一个 a 标签 -->
          <router-link to='/user/foo'>Go to Foo</router-link>
          <router-link to='/user/bar'>Go to Bar</router-link>
     </p>
     <!-- 路由出口,匹配到的组件将渲染在这里 -->
     <router-view></router-view>
</div>

const User = {
     template: `
          <div>
               <h1>User {{ $route.params.id }}</h1>
          </div>
     `
}
const router = new VueRouter({
     routes: [
          { path: '/user/:id', component: User }
     ]
})
new Vue({
     router
}).$mount('#app')

二. 动态路由匹配

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,有一个user组件,对于所有ID各不相同的用户,都要使用这个组件来渲染,那么可以在 vue-router 的路由路径中使用动态路径参数

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

const router = new VueRouter({
     routes: [
          {path: '/user/:id', component: User}
     ]
})

现在,像 user/foo 和 user/bar 都将映射到相同的路由。一个路径参数使用冒号(:)标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用

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

模式 匹配路径 $route.params
/user/:username /user/evan { username: "evan" }
/user/:username/post/:post_id /user/evan/post/123 { username: "evan", post_id: 123 }

当使用路由参数时,原来组件实例会被复用,意味着组件的生命周期钩子不会再被调用,如果想对路由参数的变化作出响应,可以简单的 watch $route 对象,或者使用beforeRouterUpdate

const User = {
     template: '...',
     watch: {
          '$route' (to, from) {
               // 对路由变化做出响应
          }
     },
     beforeRouteUpdate (to, from, next) {
     }
}  

三. 嵌套路由

一个被渲染组件同样可以包含自己的嵌套 <router-view>,例如,在User组件的模板中添加一个 <router-view>

const User = {
     template: `
          <div>
               <h1>User {{ $route.params.id }}</h1>
               <router-view></router-view>
          </div>
     `
}

要在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置

const router = new VueRouter({
     routes: [
          { path: '/user/:id', component: user,
            children: [
               {
                    // 当 /user/:id/profile 匹配成功,UserProfile 会被渲染在 User 的 <router-view> 中
                    path: 'profile',
                    component: UserProfile
               },
               {
                    path: 'posts',
                    component: UserPosts
               }
            ]
     ]
})

四.编程式的导航

除了使用<router-link> 创建 a 标签来定义导航链接,还可以借助 router 实例方法,通过编写代码来实现。想要导航到不同的URL,则使用router.push 方法。这个方法会向 history 栈添加一个新的记录,所以当用户点击浏览器后退按钮时,则回到之前的URL。

当点击 <router-link> 时,这个方法会在内部调用,所以说,点击 <router-link :to='...'> 等同于调用router.push(...)

<!-- onComplete 和 onAbort 将会在导航成功完成(在所有的异步钩子被解析之后)或终止(导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由)的时候进行相应的调用 -->
router.push(location, onComplete?, onAbort?)
<!-- 跟router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是替换掉当前的history记录 -->
router.replace(location, onComplete?, onAbort?)

该方法的参数可以是一个字符串路径,或者一个描述地址的对象,例如:

// 字符串
router.push('home')
//对象
router.push({ path: 'home'})
//命名的路由
router.push({ name: 'user', params: { userId: 123 }})
//带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

如果提供了 path, params 会被忽略,同样的规则也适用于 router-link 组件的 to 属性

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

如果目的地和当前路由相同,只有参数发生了变化(比如从一个用户资料到另一个 /users/1 -> /users/2),需要使用 beforeRouteUpdate 来响应这个变化(比如抓取用户信息)

router.go(n),这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)

// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进3步记录
router.go(3)
// 如果 history 记录不够用则会失败

五. 命名路由

可以在创建 Router 实例的时候,在 routers 配置中给某个路由设置名称

const router = new VueRouter({
   routes: [
      {
         path: '/user/:userId',
         name: 'user',
         component: User
      }
   ]
})

要链接到一个命名路由,可以传一个对象:

<router-link :to='{ name: 'user', params: { userId: 123 }}'>User</router-link>

// 也可以使用 router.push() 方法,这两种方式都会把路由导航到 /user/123 路径
router.push({ name: 'user', params: { userId: 123 }})

六. 命名视图

如果想同时展示(同级)展示多个视图,而不是嵌套展示,则可以使用命名视图,如果 router-view 没有设置名字,那么默认为 default

<router-view></router-view>
<router-view name='a'></router-view>
<router-view name='b'></router-view>

//一个视图使用一个组件渲染,对于同个路由,多个视图就需要多个组件。使用 components(带上 s) 配置
const router = new VueRouter({
   routes: [
      {
         path: '/',
         components: {
            default: Foo,
            a: Bar,
            b: Baz
         }
      }
   ]
})

七. 重定向和别名

重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b:

const router = new VueRouter({
   routers: [
      { path: '/a', redirect: '/b'}
   ]
})

重定向的目标也可以是一个命名的路由:

const router = new VueRouter({
   routes: [
      { path: '/a', redirect: { name: 'foo' }}
   ]
})

甚至是一个方法,动态返回重定向目标:

const router = new VueRouter({
     routes: [
          { path: '/a', redirect: to => {
               // 方法接受目标路由作为参数
               // return 重定向的字符串路径/路径对象
          }}
     ]
})

重定向的意思是,当用户方法 /a 时,URL将会被替换成 /b,然后匹配路由为 /b。
/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,当时路由匹配规则为 /a,就像用户访问 /a 一样

const router = new VueRouter({
     routes: [
          { path: '/a', component: A, alias: '/b'}
     ]
})

八. 路由组件传参

在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的URL上使用,使用 props 将组件和路由解耦

const User = {
     template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
     routes: [
          { path: '/user/:id', component: User}
     ]
})

通过 props 解耦

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 是静态的时候有用

const router = new VueRouter({
     routes: [
          { path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false }}
     ]
})

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

const router = new VueRouter({
     routes: [
          { path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
     ]
})
// URL /search?q=vue 会将 {query: 'vue'}作为属性传递给 SearchUser 组件

九. 导航守卫

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航,参数或查询的改变并不会触发进入/离开的导航守卫,可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫

可以使用 router.beforeEach 注册一个全局前置守卫

const router = new VueRouter({ ... })

router.beforeEach(to, from, next) => {
     //....
}

当一个导航触发时,全局配置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中

每个守卫方法接收三个参数:

  1. to:即将要进入的目标路由对象
  2. from:当前导航正要离开的路由
  3. next:一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数
  • next():进行管道中的下一个钩子,如果全部钩子执行完了,则导航状态就是 confirmed(确认的)
  • next(false):中断当前的导航。如果浏览器的URL改变了(可能是用户手动或者浏览器后退按钮),那么URL地址会重置到 from 路由对应的地址
  • next('/') 或者 next({ path: '/' }):跳转到一个不同的地址。当前导航被中断,然后进行一个新的导航。可以向 - next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项
  • next(error):如果传入 next 的参数时一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调

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

可以用 router.beforeResolve 注册一个全局解析守卫,与 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就会被调用

也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身

router.afterEach((to, from) => {
     // ...
})

可以在路由配置上直接定义路由独享守卫 beforeEnter,这些守卫与全局前置守卫的方法参数时一样的

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

也可以在路由组件内直接定义以下路由导航守卫:

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

完整的导航解析流程

  1. 导航被触发
  2. 在失活的组件里调用离开守卫
  3. 调用全局的 beforeEach 守卫
  4. 在重用的组件里调用 beforeRouteUpdate 守卫
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫
  9. 导航被确认
  10. 调用全局的 afterEach 钩子
  11. 触发 DOM 更新
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数

十. 路由元信息

定义路由的时候可以配置meta字段

const router = new VueRouter({
    routes: [{
        path: '/foo',
        component: Foo,
        children: [{
            path: 'bar',
            component: Bar,
            meta: { requiresAuth: true }
        }]
    }]
})

routes配置中的每个路由对象为路由记录,路由记录可以嵌套,当一个路由匹配成功后,它可能匹配多个路由记录。比如上面的路由配置,/foo/bar 这个URL将会匹配父路由及子路由

一个路由匹配到的所有路由记录会暴露为 route 对象的 matched 数组,我们可以遍历route.matched 来检查路由记录中的 meta 字段。下面的例子展示在全局导航守卫中检查元字段

router.beforeEach((to, from, next) => {
    if (to.matched.some(record => record.meta.requiresAuth)) {
        if (!auth.loggedIn()) { // 如果需要登录授权而没有登录授权
            next({
                path: '/login',
                query: { redirect: to.fullPath }
            })
        } else next()
    } else next()
})

十一. 过渡动效

<router-view> 是基本的动态组件,所以可以用 <transition> 组件给它添加一些过渡效果:

<transition>
     <router-view></router-view>
</transition>

该方法会给所有的路由设置一样的过渡效果,如果想让每个路由组件有各自的过渡效果,可以在各自路由组件内使用 <transition> 并设置不同的name

const Foo = {
     template: `
          <transition name='slide'>
               <div class='foo'>...</div>
          </transition>
     `
}

const Bar = {
     template: `
          <transition name='fade'>
               <div class='bar'>...</div>
          </transition>
     `
}

还可以基于当前路由与目标路由的变化关系,动态设置过渡效果:

<!-- 使用动态的 transition name -->
<transition :name='transitionName'>
     <router-view></router-view>
</transition>

// 接着在父组件内 watch $route 决定使用哪种过渡
watch: {
     '$route' (to, from) {
          const toDepth = to.path.split('/').length
          const fromDepth = from.path.split('/').length
          this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
     }
}

十二. 数据获取

在进入某个路由后,需要从服务器获取数据,可以通过两种方式来实现:

  • 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据
  • 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航

导航完成后获取数据时,会马上导航和渲染组件,然后在组件的 created 钩子中获取数据

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

导航完成前获取数据,可以在组件的 beforeRouteEnter 守卫中获取,获取成功后调用 next 方法

beforeRouterEnter (to, from, next) {
     getPost(to.params.id, (err, data) => {
          next(vm => vm.setData(err, data))
     })
},
//路由改变前,组件已经渲染完了
beforeRouteUpdate (to, from, next) {
     getPost(to.params.id, (err, data) => {
          this.setData(err, data)
          next()
     })
},
methods: {
     setData (err, data) {}
}

十三. 滚动行为

使用前端路由,当切换到新路由时,想要页面滚动到顶部,或者是保持原先的滚动位置,可以使用 scrollBehavior 方法

const router = new VueRouter({
     routes: [...],
     scrollBehavior (to, from, savedPosition) {
          // return 期望滚动到的位置
     }
})

scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savePosition 当且仅当 popstate导航(通过浏览器的 前进/后退 按钮触发)时才可用

这个方法返回滚动位置的对象信息

  • { x: number, y: number}
  • { selector: string, offset?: { x: number, y: number }}

如果返回一个falsy,或者是一个空对象,那么不会发生滚动

// 对于所有路由导航,简单的让页面滚动到顶部
scrollBehavior (to, from, savedPosition) {
     return { x: 0, y: 0}
}

如果返回 savedPosition,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样。如果要模拟滚动到锚点的行为:

scrollBehavior (to, from, savedPosition) {
     if (to.hash) {
          return {
               selector: to.hash
          }
     }
}

异步滚动,可以返回一个Promise来得出预期的位置描述

scrollBehavior (to, from, savedPosition) {
     return new Promise((resolve, reject) => {
          setTimeout(() => {
               resolve({ x: 0, y: 0 })
          }, 500)
     })
}

相关文章

网友评论

      本文标题:前端笔记 — VueRouter

      本文链接:https://www.haomeiwen.com/subject/dstztktx.html