Skip to content

等待导航结果 | Vue Router

理解导航生命周期

在 Vue Router 中,导航是异步操作。了解如何正确处理导航结果对于构建健壮的应用至关重要。导航可能因为多种原因被阻止或重定向,我们需要能够检测和处理这些情况。

导航可能被阻止的情况

  1. 用户已在目标页面 - 尝试导航到当前所在页面
  2. 导航守卫中断 - 守卫中返回 false
  3. 新导航覆盖 - 在当前导航完成前发起新导航
  4. 重定向发生 - 守卫返回新位置进行重定向
  5. 错误抛出 - 导航过程中发生错误

检测导航结果

基本模式

javascript
// 发起导航并等待结果
const navigationResult = await router.push('/my-profile')

if (navigationResult) {
  // 导航被阻止 - navigationResult 是一个 Navigation Failure 对象
  console.log('导航被阻止:', navigationResult)
} else {
  // 导航成功完成
  console.log('导航成功')
  this.isMenuOpen = false
}

实际应用示例

javascript
async function navigateToProfile() {
  try {
    const result = await router.push('/profile')
    
    if (result) {
      // 处理导航被阻止的情况
      handleNavigationFailure(result)
    } else {
      // 导航成功,执行后续操作
      closeMobileMenu()
      showSuccessNotification('页面加载成功')
    }
  } catch (error) {
    // 处理导航错误
    console.error('导航错误:', error)
    showErrorNotification('页面加载失败')
  }
}

导航失败类型

Vue Router 提供了专门的工具来识别不同类型的导航失败:

导入工具函数

javascript
import { 
  NavigationFailureType, 
  isNavigationFailure 
} from 'vue-router'

检测特定类型的失败

javascript
const failure = await router.push('/some-route')

if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
  // 导航被守卫中止
  showToast('您有未保存的更改,确定要离开吗?')
} else if (isNavigationFailure(failure, NavigationFailureType.cancelled)) {
  // 导航被新导航取消
  console.log('导航被新请求取消')
} else if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
  // 尝试导航到当前页面
  console.log('您已经在目标页面')
}

简化检测

javascript
// 只检查是否是导航失败(不关心具体类型)
if (isNavigationFailure(failure)) {
  console.log('这是一个导航失败')
}

导航失败类型详解

1. aborted - 被中止的导航

触发条件:导航守卫中返回 false

javascript
router.beforeEach((to, from) => {
  if (hasUnsavedChanges() && !confirm('确定要离开吗?')) {
    return false // 这会触发 aborted 失败
  }
})

// 检测
const result = await router.push('/other-page')
if (isNavigationFailure(result, NavigationFailureType.aborted)) {
  showUnsavedChangesWarning()
}

2. cancelled - 被取消的导航

触发条件:在当前导航完成前发起新的导航

javascript
// 快速连续发起多个导航
router.push('/page1')
const result = await router.push('/page2') // 这会取消前一个导航

if (isNavigationFailure(result, NavigationFailureType.cancelled)) {
  console.log('前一个导航被取消')
}

3. duplicated - 重复导航

触发条件:尝试导航到当前所在页面

javascript
// 当前已在 /home 页面
const result = await router.push('/home')

if (isNavigationFailure(result, NavigationFailureType.duplicated)) {
  console.log('重复导航到当前页面')
}

导航失败对象的属性

所有导航失败对象都包含有用的信息:

javascript
const failure = await router.push('/admin')

if (failure) {
  console.log('失败信息:', {
    type: failure.type,                    // 失败类型
    from: failure.from.path,              // 来源路径
    to: failure.to.path,                   // 目标路径
    message: failure.message              // 错误消息
  })
}

全局导航失败处理

使用 afterEach 守卫

javascript
router.afterEach((to, from, failure) => {
  if (failure) {
    // 发送错误报告到分析服务
    sendToAnalytics(to, from, failure)
    
    // 根据失败类型采取不同行动
    switch (failure.type) {
      case NavigationFailureType.aborted:
        trackUserAbortedNavigation()
        break
      case NavigationFailureType.cancelled:
        trackRapidNavigation()
        break
      case NavigationFailureType.duplicated:
        trackRedundantNavigation()
        break
    }
  }
})

错误监控集成

javascript
router.afterEach((to, from, failure) => {
  if (failure) {
    // 集成错误监控系统
    Sentry.captureException(new Error(`Navigation failed: ${failure.type}`), {
      extra: {
        from: from.path,
        to: to.path,
        failureType: failure.type
      }
    })
  }
})

检测重定向

重定向不会导致导航失败,但需要特殊处理:

检测重定向发生

javascript
const result = await router.push('/my-profile')

// 检查是否发生了重定向
if (router.currentRoute.value.redirectedFrom) {
  console.log('发生了重定向:', {
    from: router.currentRoute.value.redirectedFrom.path,
    to: router.currentRoute.value.path
  })
}

重定向示例

javascript
// 导航守卫中的重定向
router.beforeEach((to, from) => {
  if (to.path === '/admin' && !isAdmin()) {
    return '/access-denied' // 这会触发重定向
  }
})

// 使用
await router.push('/admin')
if (router.currentRoute.value.redirectedFrom) {
  console.log('从管理员页面重定向而来')
}

实际应用场景

场景 1:移动端菜单管理

javascript
async function handleMobileNavigation(path) {
  // 发起导航
  const result = await router.push(path)
  
  if (!result) {
    // 只有真正离开当前页面时才关闭菜单
    closeMobileMenu()
  }
  // 如果导航被阻止,菜单保持打开状态
}

场景 2:表单数据保护

javascript
const unsavedChanges = ref(false)

router.beforeEach((to, from) => {
  if (unsavedChanges.value && !confirm('有未保存的更改,确定要离开吗?')) {
    return false
  }
})

async function saveAndNavigate(path) {
  try {
    await saveFormData()
    const result = await router.push(path)
    
    if (result && isNavigationFailure(result, NavigationFailureType.aborted)) {
      // 用户选择不离开,保持表单状态
      console.log('用户选择留在当前页面')
    }
  } catch (error) {
    console.error('保存失败:', error)
  }
}

场景 3:导航进度指示

javascript
const isLoading = ref(false)

async function navigateWithLoading(path) {
  isLoading.value = true
  
  try {
    const result = await router.push(path)
    
    if (result) {
      // 导航被阻止,不需要隐藏加载指示器
      console.log('导航被阻止')
    } else {
      // 导航成功,隐藏加载指示器
      isLoading.value = false
    }
  } catch (error) {
    isLoading.value = false
    console.error('导航错误:', error)
  }
}

场景 4:智能重试机制

javascript
async function robustNavigation(path, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const result = await router.push(path)
      
      if (!result) {
        return true // 导航成功
      }
      
      // 如果是重复导航,不算失败
      if (isNavigationFailure(result, NavigationFailureType.duplicated)) {
        return true
      }
      
      console.log(`导航尝试 ${attempt} 失败:`, result.type)
      
      // 等待后重试
      if (attempt < maxRetries) {
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt))
      }
    } catch (error) {
      console.error(`导航尝试 ${attempt} 错误:`, error)
    }
  }
  
  throw new Error(`导航失败,已尝试 ${maxRetries} 次`)
}

最佳实践

1. 总是检查导航结果

javascript
// Good: 检查导航结果
const result = await router.push('/target')
if (result) {
  handleNavigationFailure(result)
}

// Avoid: 忽略导航结果
await router.push('/target')
// 后续代码立即执行,不知道导航是否成功

2. 提供用户反馈

javascript
async function userFriendlyNavigation(path) {
  const result = await router.push(path)
  
  if (isNavigationFailure(result, NavigationFailureType.aborted)) {
    showToast('导航被中断,可能有未保存的更改')
  } else if (isNavigationFailure(result, NavigationFailureType.duplicated)) {
    showToast('您已经在目标页面')
  }
  // 其他情况不需要特别提示
}

3. 错误边界处理

javascript
async function safeNavigation(path) {
  try {
    const result = await router.push(path)
    
    if (result) {
      // 导航失败但有预期,不抛出错误
      return { success: false, failure: result }
    }
    
    return { success: true }
  } catch (error) {
    // 真正的意外错误
    console.error('意外的导航错误:', error)
    return { success: false, error }
  }
}

4. 性能监控

javascript
async function monitoredNavigation(path) {
  const startTime = performance.now()
  
  const result = await router.push(path)
  const duration = performance.now() - startTime
  
  // 记录导航性能
  analytics.track('navigation', {
    path,
    duration,
    success: !result,
    failureType: result?.type
  })
  
  return result
}

🎯 总结:正确处理导航结果是构建高质量 Vue 应用的关键。通过理解不同的导航失败类型和适当的错误处理策略,你可以创建出更加健壮和用户友好的应用程序。

🚀 Vue Router - 让前端路由变得简单而强大 | 构建现代化单页应用的最佳选择