Skip to content

onBeforeRouteUpdate 函数 | Vue Router API

概述

onBeforeRouteUpdate 是 Vue Router 提供的组合式 API 函数,用于在组件内添加路由更新守卫。当当前路由的参数或查询参数发生变化,但组件被复用时,这个守卫会被触发。

语法

typescript
function onBeforeRouteUpdate(guard: NavigationGuard): void

参数

guard

类型: NavigationGuard必需: 是 说明: 导航守卫函数

typescript
type NavigationGuard = (
  to: RouteLocationNormalized,
  from: RouteLocationNormalized,
  next: NavigationGuardNext
) => any

基本用法

基础路由更新守卫

vue
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'

const userData = ref(null)

onBeforeRouteUpdate(async (to, from, next) => {
  // 当用户 ID 发生变化时重新加载数据
  if (to.params.id !== from.params.id) {
    userData.value = await fetchUserData(to.params.id)
  }
  next()
})
</script>

查询参数变化处理

vue
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
import { ref, watch } from 'vue'

const searchResults = ref([])
const currentQuery = ref('')

onBeforeRouteUpdate(async (to, from, next) => {
  // 当搜索查询发生变化时重新搜索
  if (to.query.q !== from.query.q) {
    currentQuery.value = to.query.q || ''
    searchResults.value = await performSearch(currentQuery.value)
  }
  next()
})
</script>

实际应用场景

场景 1:用户资料页面

vue
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
import { ref, onMounted } from 'vue'

const user = ref(null)
const loading = ref(false)

// 加载用户数据
async function loadUserData(userId) {
  loading.value = true
  try {
    user.value = await api.getUser(userId)
  } catch (error) {
    console.error('加载用户数据失败:', error)
    user.value = null
  } finally {
    loading.value = false
  }
}

// 组件挂载时加载数据
onMounted(() => {
  loadUserData($route.params.id)
})

// 路由参数变化时重新加载数据
onBeforeRouteUpdate(async (to, from, next) => {
  if (to.params.id !== from.params.id) {
    await loadUserData(to.params.id)
  }
  next()
})
</script>

<template>
  <div v-if="loading">加载中...</div>
  <div v-else-if="user">
    <h1>{{ user.name }} 的资料</h1>
    <!-- 用户信息展示 -->
  </div>
  <div v-else>用户不存在</div>
</template>

场景 2:产品列表和筛选

vue
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
import { ref, computed } from 'vue'

const products = ref([])
const filters = ref({})

// 从路由查询参数解析筛选条件
function parseFiltersFromRoute(route) {
  return {
    category: route.query.category || '',
    priceMin: parseInt(route.query.price_min) || 0,
    priceMax: parseInt(route.query.price_max) || 1000,
    sort: route.query.sort || 'name'
  }
}

// 加载产品数据
async function loadProducts(filters) {
  products.value = await api.getProducts(filters)
}

// 初始加载
onMounted(() => {
  filters.value = parseFiltersFromRoute($route)
  loadProducts(filters.value)
})

// 路由查询参数变化时更新筛选和重新加载
onBeforeRouteUpdate(async (to, from, next) => {
  const newFilters = parseFiltersFromRoute(to)
  
  // 检查筛选条件是否发生变化
  if (JSON.stringify(newFilters) !== JSON.stringify(filters.value)) {
    filters.value = newFilters
    await loadProducts(filters.value)
  }
  
  next()
})

// 计算属性:筛选后的产品
const filteredProducts = computed(() => {
  return products.value.filter(product => 
    product.price >= filters.value.priceMin &&
    product.price <= filters.value.priceMax
  )
})
</script>

场景 3:分页导航

vue
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
import { ref, computed } from 'vue'

const items = ref([])
const totalItems = ref(0)
const itemsPerPage = 10

// 计算当前页码
const currentPage = computed(() => {
  return parseInt($route.query.page) || 1
})

// 计算总页数
const totalPages = computed(() => {
  return Math.ceil(totalItems.value / itemsPerPage)
})

// 加载分页数据
async function loadPageData(page) {
  const offset = (page - 1) * itemsPerPage
  const result = await api.getItems({ offset, limit: itemsPerPage })
  items.value = result.items
  totalItems.value = result.total
}

// 初始加载
onMounted(() => {
  loadPageData(currentPage.value)
})

// 页码变化时重新加载数据
onBeforeRouteUpdate(async (to, from, next) => {
  const toPage = parseInt(to.query.page) || 1
  const fromPage = parseInt(from.query.page) || 1
  
  if (toPage !== fromPage) {
    await loadPageData(toPage)
  }
  
  next()
})
</script>

<template>
  <div class="pagination">
    <router-link 
      v-for="page in totalPages" 
      :key="page"
      :to="{ query: { page } }"
      :class="{ active: page === currentPage }"
    >
      {{ page }}
    </router-link>
  </div>
  
  <div class="items">
    <div v-for="item in items" :key="item.id">
      {{ item.name }}
    </div>
  </div>
</template>

高级用法

防抖搜索

vue
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
import { ref, watch } from 'vue'

const searchResults = ref([])
const searchTerm = ref('')
let searchTimeout = null

// 防抖搜索函数
function debouncedSearch(term) {
  return new Promise((resolve) => {
    if (searchTimeout) {
      clearTimeout(searchTimeout)
    }
    
    searchTimeout = setTimeout(async () => {
      const results = await api.search(term)
      resolve(results)
    }, 300)
  })
}

onBeforeRouteUpdate(async (to, from, next) => {
  const newTerm = to.query.q || ''
  
  if (newTerm !== searchTerm.value) {
    searchTerm.value = newTerm
    searchResults.value = await debouncedSearch(newTerm)
  }
  
  next()
})

// 清理定时器
onUnmounted(() => {
  if (searchTimeout) {
    clearTimeout(searchTimeout)
  }
})
</script>

数据预加载和缓存

vue
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'

const dataCache = new Map()

async function fetchDataWithCache(key, fetcher) {
  if (dataCache.has(key)) {
    return dataCache.get(key)
  }
  
  const data = await fetcher()
  dataCache.set(key, data)
  return data
}

onBeforeRouteUpdate(async (to, from, next) => {
  const cacheKey = to.fullPath
  
  if (to.params.id !== from.params.id) {
    const data = await fetchDataWithCache(cacheKey, () => 
      api.getDetail(to.params.id)
    )
    // 使用缓存的数据
  }
  
  next()
})
</script>

最佳实践

1. 性能优化

javascript
// 避免不必要的重渲染
onBeforeRouteUpdate(async (to, from, next) => {
  // 只在相关参数变化时执行操作
  const relevantParams = ['id', 'slug', 'category']
  const hasRelevantChange = relevantParams.some(param => 
    to.params[param] !== from.params[param]
  )
  
  if (hasRelevantChange) {
    await reloadData(to)
  }
  
  next()
})

2. 错误处理

javascript
onBeforeRouteUpdate(async (to, from, next) => {
  try {
    if (shouldReloadData(to, from)) {
      await reloadData(to)
    }
    next()
  } catch (error) {
    console.error('路由更新失败:', error)
    // 可以选择显示错误页面或重试
    showErrorNotification('数据加载失败')
    next(false) // 或 next('/error')
  }
})

3. 条件性更新

javascript
const updateConditions = {
  userProfile: (to, from) => to.params.userId !== from.params.userId,
  searchResults: (to, from) => to.query.q !== from.query.q,
  pagination: (to, from) => to.query.page !== from.query.page
}

onBeforeRouteUpdate(async (to, from, next) => {
  const conditions = Object.values(updateConditions)
  const shouldUpdate = conditions.some(condition => condition(to, from))
  
  if (shouldUpdate) {
    await handleRouteUpdate(to, from)
  }
  
  next()
})

注意事项

  1. 组件复用 - 确保组件能够正确处理参数变化
  2. 性能考虑 - 避免在每次更新时都执行昂贵操作
  3. 数据一致性 - 确保数据与当前路由参数匹配
  4. 用户体验 - 提供加载状态和错误处理

兼容性

  • Vue 3.0+
  • 组合式 API
  • TypeScript 支持

🔄 提示onBeforeRouteUpdate 是处理动态路由参数变化的强大工具,特别适合需要根据路由变化更新数据的场景。

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