编程式导航

除了使用 <router-link> 声明式导航,Vue Router 还支持编程式导航,通过 JavaScript 代码控制路由跳转。

router.push

导航到不同的 URL,向 history 栈添加新记录。

基本用法

this.$router.push('/home')

this.$router.push({ path: '/home' })

this.$router.push({ name: 'home' })

带参数

this.$router.push({
  path: '/user',
  query: {
    name: '张三',
    age: 25
  }
})

this.$router.push({
  name: 'user',
  params: {
    id: 123
  }
})

命名路由

this.$router.push({
  name: 'user',
  params: { id: 123 },
  query: { tab: 'profile' }
})

带回调

this.$router.push('/home', () => {
  console.log('导航完成')
}, (error) => {
  console.error('导航失败', error)
})

this.$router.push('/home')
  .then(() => {
    console.log('导航完成')
  })
  .catch(error => {
    console.error('导航失败', error)
  })

router.replace

替换当前路由,不会向 history 栈添加新记录。

this.$router.replace('/home')

this.$router.replace({ name: 'home' })

this.$router.replace({
  path: '/user',
  query: { id: 123 }
})

router-link 的 replace

<router-link to="/home" replace>首页</router-link>

router.go

在 history 栈中前进或后退。

this.$router.go(1)
this.$router.go(-1)
this.$router.go(3)
this.$router.go(-2)

this.$router.go(100)

router.back

后退一步,等同于 router.go(-1)

this.$router.back()

router.forward

前进一步,等同于 router.go(1)

this.$router.forward()

导航参数详解

path + query

this.$router.push({
  path: '/search',
  query: {
    q: 'vue',
    page: 1,
    size: 10
  }
})

name + params

this.$router.push({
  name: 'user',
  params: { id: 123 }
})

注意:path 和 params 不能同时使用

this.$router.push({
  path: '/user',
  params: { id: 123 }
})

params 会被忽略,应该使用 name 或 path + query。

完整导航对象

this.$router.push({
  name: 'user',
  params: { id: 123 },
  query: { tab: 'profile' },
  hash: '#section1'
})

导航确认

onAbort 和 onSuccess

this.$router.push(
  '/home',
  () => {
    console.log('导航成功')
  },
  () => {
    console.log('导航中止')
  }
)

Promise 形式

try {
  await this.$router.push('/home')
  console.log('导航成功')
} catch (error) {
  if (error.name === 'NavigationDuplicated') {
    console.log('重复导航')
  } else {
    console.error('导航失败', error)
  }
}

导航失败处理

捕获导航错误

this.$router.push('/login').catch(error => {
  if (error.name !== 'NavigationDuplicated') {
    console.error(error)
  }
})

全局错误处理

const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
  return originalPush.call(this, location).catch(error => {
    if (error.name !== 'NavigationDuplicated') {
      return Promise.reject(error)
    }
  })
}

实战示例

登录后跳转

export default {
  methods: {
    async login() {
      try {
        await this.$store.dispatch('login', this.form)
        
        const redirect = this.$route.query.redirect || '/'
        this.$router.replace(redirect)
      } catch (error) {
        this.$message.error(error.message)
      }
    }
  }
}

返回上一页

<template>
  <button @click="goBack">返回</button>
</template>

<script>
export default {
  methods: {
    goBack() {
      if (window.history.length > 1) {
        this.$router.back()
      } else {
        this.$router.push('/')
      }
    }
  }
}
</script>

条件导航

export default {
  methods: {
    navigateTo(route) {
      if (this.hasUnsavedChanges) {
        const answer = window.confirm('有未保存的更改,确定离开?')
        if (!answer) return
      }
      
      this.$router.push(route)
    }
  }
}

打开新标签页

const routeData = this.$router.resolve({
  name: 'user',
  params: { id: 123 }
})
window.open(routeData.href, '_blank')

刷新当前页面

this.$router.go(0)

location.reload()

切换 Tab

export default {
  methods: {
    switchTab(tab) {
      this.$router.replace({
        query: {
          ...this.$route.query,
          tab
        }
      })
    }
  }
}

导航守卫配合

登录检查

export default {
  methods: {
    async goToAdmin() {
      if (!this.isLoggedIn) {
        this.$router.push({
          path: '/login',
          query: { redirect: '/admin' }
        })
        return
      }
      
      this.$router.push('/admin')
    }
  }
}

权限检查

export default {
  methods: {
    async navigateWithPermission(route) {
      const hasPermission = await this.checkPermission(route)
      
      if (hasPermission) {
        this.$router.push(route)
      } else {
        this.$message.error('没有访问权限')
        this.$router.push('/403')
      }
    }
  }
}

路由跳转动画

export default {
  data() {
    return {
      transitionName: 'slide-left'
    }
  },
  watch: {
    '$route'(to, from) {
      const toDepth = to.path.split('/').length
      const fromDepth = from.path.split('/').length
      this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
    }
  }
}
<transition :name="transitionName">
  <router-view></router-view>
</transition>

小结

编程式导航要点:

  1. router.push:添加新历史记录
  2. router.replace:替换当前记录
  3. router.go:前进或后退
  4. 参数传递:path + query 或 name + params
  5. 错误处理:捕获 NavigationDuplicated 错误