Skip to content
This repository has been archived by the owner on Feb 9, 2021. It is now read-only.

vuejs router collections #21

Open
lbwa opened this issue Jul 6, 2018 · 2 comments
Open

vuejs router collections #21

lbwa opened this issue Jul 6, 2018 · 2 comments
Labels
optimization optimized solutions Vue.js Vue.js / Vue-router / Vue-cli / Vuex .etc

Comments

@lbwa
Copy link
Owner

lbwa commented Jul 6, 2018

浏览器默认跳转行为

现象

vuejs 应用中,有些链接会刷新控制台,有些不会,即有部分刷新控制台的链接执行了多页面跳转。这引起了自己的思考。

多页面跳转的弊端有:

  1. <a> 标签执行多页面跳转时,无法在跳转过程中处理过场动画的问题。用户体验差,页面出现冻结。

  2. 多页面跳转将会刷新整个页面,那么将导致无关组件的不必要的重建刷新。

本质

vue-router 处理了页面的跳转,阻止了浏览器默认的多页面跳转行为。达到了 更新特定组件的目的(原 <router-view/> 区域)。而浏览器的默认行为会刷新 所有 组件。

解决方案

  • 编程式导航

手动阻止浏览器的默认跳转行为,调用 this.$router.push() 执行页面跳转。

<a class="link-item"
  :href='`/${destination}`'
  @click.stop.prevent="navigate"
>{{content}}</a>
navigate (evt) {
  const href = evt.target.getAttribute('href')
  this.$router.push(href)
}
  • vue-router 全权处理页面跳转

模块中的 <a> 标签亦可替换为 <router-link> 标签,这样会在 vue-router 内部调用 this.$router.push() 该方法,并阻止链接的默认多页面跳转行为。

<router-link class="link-item" :to='`/${destination}`'>{{content}}</router-link>

总结

组件中的跳转行为是一个容易忽视的对性能有影响的地方。只有阻止了浏览器的默认多页面跳转行为才能将组件的刷新范围降到最低。否则浏览器的默认多页面跳转行为将刷新整个页面。

@lbwa lbwa added optimization optimized solutions Vue.js Vue.js / Vue-router / Vue-cli / Vuex .etc labels Jul 6, 2018
@lbwa
Copy link
Owner Author

lbwa commented Jul 7, 2018

不同路径跳转

// 字符串
vm.$router.push('home')

// 对象
vm.$router.push({ path: 'home' })

// 命名的路由
vm.$router.push({ name: 'user', params: { userId: 123 }})

// 带查询参数,变成 /register?plan=private
vm.$router.push({ path: `/user/${userId}` })
vm.$router.push({ path: 'register', query: { plan: 'private' }})

其中 pathquery 互斥,path 会覆盖 query

路由传参

使用场景:需要得到当前路由的路由信息对象。

vm.$route 表示路由信息对象,它包含当前激活的路由的状态信息。

例如,vm.$route.params 得到 路由下的 params 参数,它包含路由匹配的动态片段。

当匹配路径为 /app/:id 时,那么匹配 https://example.com/app/123 将得到 params 为 vm.$route.params === { id: 123 }

但是,一般为了防止 vm.$route 与组件高度耦合,不在组件中直接使用 vm.$route 对象。

在组件中与 $route 解耦

路由 route 配置开启 props 选项,那么 $route.params 将会被作为 props 对象的属性传入组件,成为组件属性。

// routes.js 路径列表
export default [
  // ...
  {
    path: '/app/:id',
    component: App,

    // $route.params 将会被设置为组件属性
    props: true,

    // props 亦可指定对象传递给组件
    props: {
      id: 'hello'
    },

    // props 亦可传递方法返回一个对象
    props: (route) => ({
      query: route.query.q
    })
  }
]

// component
export default {
  props: ['id'], // id 将成为组件的属性

  // ...
}

类似地,我们同样可以将 querypath路由信息对象与组件解耦。

经过以上配置后,在组件中就可以不用调用 $route 就可以得到路由信息对象中的属性了。

参考:路由传参解耦

导航守卫

beforeEach 导航守卫

适用场景:在路由跳转之前,检测用户登录状态,未登录则跳转登录页面。

// 全局守卫
/**
 * @param {Object} to      - 即将进入的目标路由对象
 * @param {Object} from    - 当前导航正要离开的路由
 * @param {Function} next  - 导航 resolved 钩子
 */
router.beforeEach(to, from, next) {
  if (to.fullPath === '/app') { // 检测登录状态
    // 未登录时,跳转至登录界面
    next('/login')
  } else {
    // 正常操作
    // ...
    next()
  }
}

导航守卫是异步解析执行,那么 next() 函数是必须的。

beforeResolve 全局解析守卫

在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫被调用。

更多的守卫介绍:导航守卫

组件内守卫

beforeRouteEnter: (to, from, next) => {
  // 因为当守卫执行前,组件实例还没被创建,所以在 beforeRouteEnter 中不能使用 this 访问 vue 实例
  next(vm => {
    // 在 next 回调中使用 vm 可访问 vue 实例。
    console.log('%c Component before enter, loading data', 'color: dodgerblue')
  })
},

/**
 * 1. 在当前路由改变,且该组件被复用时调用。举例来说,对于一个带有动态参数的路径
 * '/app/:id',在'/app/1' 和 '/app/2' 之间跳转的时候,由于会渲染同样的 App 组件,因此
 * 组件实例会被复用。而这个钩子就会在这个情况下被调用。
 * 2. 可以访问组件实例 `this`
 * 3. 适用场景:基于同一基础 url 动态匹配路由且复用组件时,获取下一路由的 ajax 数据。
 */
beforeRouteUpdate: (to, from, next) => {
  // 需全局路由配置 '/app/:dynamicId'和 props: true,组件内配置 props: ['dynamicId']
  if (this.dynamicId === '123') {
    getSomeData(this.dynamicId)
  }
  console.log('%c Component before update', 'color: dodgerblue')
  next()
},

beforeRouteLeave: (to, from, next) => {
  /**
   * 1. 导航离开该组件的对应路由时调用
   * 2. 可以访问组件实例 `this`
   * 3. 适用场景:在用户离开当前页面时,可发出表单未保存提醒
   */
  console.log('%c Component before leave', 'color: red')
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (answer) {
    next()
  } else {
    next(false) // 传入 false 来取消路由跳转
  }
}

在组件内守卫中,特别地,在 beforeRouteEnter 中是不能使用 this 来访问 vue 实例的。因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdatebeforeRouteLeave 来说,this 已经可用了,所以不支持传递回调。

@lbwa
Copy link
Owner Author

lbwa commented Jul 9, 2018

如何监听组件的原生事件

  • 场景:如何监听类似于<router-link>vuejs 组件的根元素的原生事件

解决方案: 使用 .native 修饰符

<router-link @click.stop.native="activateLoading">click</router-link>

Notice: 除非监听事件的回调需要传入特定的参数,那么应该在多个相同自定义组件的祖先元素上使用事件委托,而不是给每个相同结构的自定义组件添加事件监听程序。

<nav class="tag-list" slot="main" @click.stop="doSomething">
  <router-link
    class="link-item"
    v-for="item in items"
    :key="item.id"
    :to="`${item.to}`"
  >{{item.body}}</router-link>
</nav>

Reference

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
optimization optimized solutions Vue.js Vue.js / Vue-router / Vue-cli / Vuex .etc
Projects
None yet
Development

No branches or pull requests

1 participant