{T}

API 参考

动态导入语法

javascript
import('path/to/component.vue')

Webpack 魔法注释

注释说明
/* webpackChunkName: "name" */指定 chunk 名称
/* webpackPrefetch: true */预加载
/* webpackPreload: true */预加载(并行)
/* webpackMode: "lazy" */懒加载模式

滚动行为

基本用法

使用 scrollBehavior 配置路由切换时的滚动位置:

javascript
const router = new VueRouter({
  routes: [...],
  scrollBehavior(to, from, savedPosition) {
    // 返回滚动位置
  }
})

参数说明

参数类型说明
toRoute目标路由对象
fromRoute来源路由对象
savedPositionObject | null浏览器前进/后退时的保存位置

返回值类型

javascript
const router = new VueRouter({
  routes: [...],
  scrollBehavior(to, from, savedPosition) {
    // 1. 返回 { x, y } - 滚动到指定位置
    return { x: 0, y: 0 }
    
    // 2. 返回 savedPosition - 恢复保存的位置
    if (savedPosition) {
      return savedPosition
    }
    
    // 3. 返回 { selector } - 滚动到锚点
    if (to.hash) {
      return { selector: to.hash }
    }
    
    // 4. 返回 false - 不滚动
    return false
  }
})

常用滚动行为

滚动到顶部

javascript
scrollBehavior(to, from, savedPosition) {
  return { x: 0, y: 0 }
}

保持滚动位置

javascript
scrollBehavior(to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition
  } else {
    return { x: 0, y: 0 }
  }
}

滚动到锚点

javascript
scrollBehavior(to, from, savedPosition) {
  if (to.hash) {
    return {
      selector: to.hash,
      behavior: 'smooth'  // 平滑滚动
    }
  }
}

条件滚动

javascript
scrollBehavior(to, from, savedPosition) {
  // 特定路由保持位置
  if (to.meta.keepScroll) {
    return savedPosition || false
  }
  
  // 默认滚动到顶部
  return { x: 0, y: 0 }
}

异步滚动

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

完整示例

javascript
const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/',
      component: Home,
      meta: { title: '首页' }
    },
    {
      path: '/article/:id',
      component: Article,
      meta: { title: '文章详情', keepScroll: true }
    }
  ],
  
  scrollBehavior(to, from, savedPosition) {
    // 前进/后退时恢复位置
    if (savedPosition) {
      return savedPosition
    }
    
    // 滚动到锚点
    if (to.hash) {
      return {
        selector: to.hash,
        behavior: 'smooth',
        offset: { x: 0, y: 60 }  // 偏移量(固定头部高度)
      }
    }
    
    // 特定路由保持位置
    if (to.meta.keepScroll) {
      return false
    }
    
    // 默认滚动到顶部
    return { x: 0, y: 0, behavior: 'smooth' }
  }
})

路由元信息

定义元信息

在路由配置中通过 meta 属性定义:

javascript
const routes = [
  {
    path: '/home',
    component: Home,
    meta: {
      title: '首页',
      requiresAuth: false
    }
  },
  {
    path: '/admin',
    component: Admin,
    meta: {
      title: '管理后台',
      requiresAuth: true,
      roles: ['admin', 'super_admin']
    }
  }
]

访问元信息

javascript
// 在路由守卫中
router.beforeEach((to, from, next) => {
  // 获取标题
  document.title = to.meta.title || '默认标题'
  
  // 检查是否需要认证
  if (to.meta.requiresAuth) {
    // ...
  }
  
  next()
})

元信息继承

子路由会继承父路由的 meta

javascript
const routes = [
  {
    path: '/admin',
    component: Admin,
    meta: { requiresAuth: true },
    children: [
      {
        path: 'user',
        component: UserManage,
        meta: { title: '用户管理' }
        // 实际 meta: { requiresAuth: true, title: '用户管理' }
      }
    ]
  }
]

路由记录遍历

使用 to.matched 遍历所有路由记录:

javascript
router.beforeEach((to, from, next) => {
  // 检查任意层级是否需要认证
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!isAuthenticated()) {
      next('/login')
      return
    }
  }
  
  next()
})

完整示例

javascript
const routes = [
  {
    path: '/',
    component: Layout,
    meta: { requiresAuth: true },
    children: [
      {
        path: '',
        name: 'Dashboard',
        component: Dashboard,
        meta: { 
          title: '仪表盘',
          icon: 'dashboard',
          breadcrumb: ['首页', '仪表盘']
        }
      },
      {
        path: 'user',
        name: 'UserManage',
        component: UserManage,
        meta: { 
          title: '用户管理',
          icon: 'user',
          roles: ['admin'],
          breadcrumb: ['首页', '用户管理']
        }
      },
      {
        path: 'user/:id',
        name: 'UserDetail',
        component: UserDetail,
        meta: { 
          title: '用户详情',
          hidden: true,  // 不在菜单显示
          activeMenu: '/user'  // 高亮的菜单项
        }
      }
    ]
  }
]

// 使用 meta 信息
router.beforeEach((to, from, next) => {
  // 设置标题
  const title = to.meta.title
  document.title = title ? `${title} - 管理系统` : '管理系统'
  
  // 权限检查
  if (to.matched.some(record => record.meta.roles)) {
    const roles = to.meta.roles
    const userRole = store.state.user.role
    
    if (!roles.includes(userRole)) {
      next('/403')
      return
    }
  }
  
  // 认证检查
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!store.getters.isLoggedIn) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
      return
    }
  }
  
  next()
})

过渡动效

基本用法

使用 <transition> 包裹 <router-view>

Vue SFC
<template>
  <div id="app">
    <transition name="fade" mode="out-in">
      <router-view />
    </transition>
  </div>
</template>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

过渡模式

模式说明
out-in先离开再进入
in-out先进入再离开
同时进行

动态过渡

根据路由元信息设置不同过渡:

Vue SFC
<template>
  <div id="app">
    <transition :name="transitionName" mode="out-in">
      <router-view :key="$route.fullPath" />
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      transitionName: 'fade'
    }
  },
  
  watch: {
    '$route'(to, from) {
      // 根据路由层级决定过渡方向
      const toDepth = to.path.split('/').length
      const fromDepth = from.path.split('/').length
      this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
      
      // 或使用路由 meta 指定过渡
      this.transitionName = to.meta.transition || 'fade'
    }
  }
}
</script>

<style>
/* 淡入淡出 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

/* 左右滑动 */
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
  transition: transform 0.3s ease;
  position: absolute;
  width: 100%;
}

.slide-left-enter-from {
  transform: translateX(100%);
}

.slide-left-leave-to {
  transform: translateX(-100%);
}

.slide-right-enter-from {
  transform: translateX(-100%);
}

.slide-right-leave-to {
  transform: translateX(100%);
}
</style>

路由级过渡

在路由配置中指定过渡:

javascript
const routes = [
  {
    path: '/home',
    component: Home,
    meta: { transition: 'slide-up' }
  },
  {
    path: '/about',
    component: About,
    meta: { transition: 'fade' }
  }
]
Vue SFC
<template>
  <transition :name="$route.meta.transition || 'fade'" mode="out-in">
    <router-view />
  </transition>
</template>

数据获取

导航后获取

进入路由后获取数据,显示加载状态:

Vue SFC
<template>
  <div class="post">
    <div v-if="loading" class="loading">加载中...</div>
    <div v-else>
      <h2>{{ post.title }}</h2>
      <p>{{ post.content }}</p>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      loading: false,
      post: null
    }
  },
  
  watch: {
    '$route': 'fetchData'
  },
  
  created() {
    this.fetchData()
  },
  
  methods: {
    async fetchData() {
      this.loading = true
      try {
        const response = await fetch(`/api/post/${this.$route.params.id}`)
        this.post = await response.json()
      } catch (error) {
        console.error('获取数据失败:', error)
      } finally {
        this.loading = false
      }
    }
  }
}
</script>

导航前获取

使用 beforeRouteEnter 在导航完成前获取数据:

Vue SFC
<script>
export default {
  data() {
    return {
      post: null
    }
  },
  
  beforeRouteEnter(to, from, next) {
    // 此时组件实例未创建,无法访问 this
    fetch(`/api/post/${to.params.id}`)
      .then(response => response.json())
      .then(post => {
        // 通过回调访问组件实例
        next(vm => {
          vm.post = post
        })
      })
      .catch(() => {
        next('/404')
      })
  },
  
  beforeRouteUpdate(to, from, next) {
    // 路由参数变化时
    this.post = null
    fetch(`/api/post/${to.params.id}`)
      .then(response => response.json())
      .then(post => {
        this.post = post
        next()
      })
      .catch(() => {
        next('/404')
      })
  }
}
</script>

使用 Vuex

javascript
// store/modules/post.js
export default {
  state: {
    post: null,
    loading: false
  },
  
  mutations: {
    SET_POST(state, post) {
      state.post = post
    },
    SET_LOADING(state, loading) {
      state.loading = loading
    }
  },
  
  actions: {
    async fetchPost({ commit }, id) {
      commit('SET_LOADING', true)
      try {
        const response = await fetch(`/api/post/${id}`)
        const post = await response.json()
        commit('SET_POST', post)
      } finally {
        commit('SET_LOADING', false)
      }
    }
  }
}
javascript
// 路由配置
const routes = [
  {
    path: '/post/:id',
    component: Post,
    beforeEnter: (to, from, next) => {
      store.dispatch('post/fetchPost', to.params.id)
        .then(() => next())
        .catch(() => next('/404'))
    }
  }
]

导航故障

导航失败类型

Vue Router 3.4+ 提供导航失败检测:

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

// NavigationFailureType 枚举
NavigationFailureType.aborted      // 导航被守卫中止
NavigationFailureType.cancelled    // 新导航开始了
NavigationFailureType.duplicated   // 目标位置与当前位置相同

检测导航失败

javascript
router.push('/admin').catch(failure => {
  if (isNavigationFailure(failure)) {
    console.log('导航失败类型:', failure.type)
    console.log('目标路由:', failure.to)
    console.log('来源路由:', failure.from)
  }
})

处理导航失败

javascript
// 全局处理
router.beforeEach((to, from, next) => {
  // 某些条件下中止导航
  if (shouldAbort) {
    next(false)  // 中止导航
    return
  }
  next()
})

// 捕获导航失败
this.$router.push('/admin')
  .catch(failure => {
    if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
      console.log('导航被中止')
    }
  })

重复导航处理

javascript
// Vue Router 3.1+ 会抛出 NavigationDuplicated 错误
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
  return originalPush.call(this, location).catch(err => {
    if (err.name !== 'NavigationDuplicated') {
      return Promise.reject(err)
    }
  })
}

动态路由

添加路由

javascript
// 添加单个路由
router.addRoute({
  path: '/new-route',
  component: NewRoute
})

// 添加嵌套路由
router.addRoute('parentRoute', {
  path: 'child',
  component: ChildRoute
})

删除路由

javascript
// 通过名称删除路由
router.removeRoute('routeName')

查询路由

javascript
// 获取所有路由记录
router.getRoutes()

// 检查路由是否存在
router.hasRoute('routeName')

动态路由示例

javascript
// 权限路由动态添加
export const asyncRoutes = [
  {
    path: '/admin',
    name: 'Admin',
    component: () => import('@/views/Admin.vue'),
    meta: { roles: ['admin'] }
  }
]

// 根据用户角色添加路由
export function addDynamicRoutes(roles) {
  const routes = asyncRoutes.filter(route => {
    if (route.meta && route.meta.roles) {
      return roles.some(role => route.meta.roles.includes(role))
    }
    return true
  })
  
  routes.forEach(route => {
    router.addRoute(route)
  })
}

路由 API 详解

router.resolve

解析路由位置,返回路由信息和 URL:

javascript
const resolved = router.resolve({
  name: 'UserDetail',
  params: { id: 123 },
  query: { tab: 'profile' }
})

console.log(resolved.href)     // '/user/123?tab=profile'
console.log(resolved.route)    // 路由对象
console.log(resolved.location) // 位置对象

router.currentRoute

获取当前路由对象:

javascript
// 在组件内
this.$route

// 在组件外
router.currentRoute

router.app

获取挂载的 Vue 根实例:

javascript
const app = router.app

最佳实践

1. 合理组织路由文件

code
router/
├── index.js          # 主路由文件
├── modules/          # 路由模块
│   ├── user.js
│   ├── product.js
│   └── admin.js
└── guards.js         # 路由守卫

2. 使用路由常量

javascript
// constants/routes.js
export const ROUTE_NAMES = {
  HOME: 'Home',
  USER_LIST: 'UserList',
  USER_DETAIL: 'UserDetail'
}

export const ROUTE_PATHS = {
  HOME: '/',
  USER: '/user',
  ADMIN: '/admin'
}

3. 统一错误处理

javascript
// 路由错误处理
router.onError(error => {
  console.error('路由错误:', error)
  // 跳转到错误页面
  router.push('/error')
})

// 处理懒加载失败
const lazyLoad = (view) => {
  return () => import(`@/views/${view}.vue`).catch(() => {
    router.push('/error')
  })
}

4. 路由过渡优化

Vue SFC
<template>
  <!-- 使用 keep-alive 缓存 -->
  <router-view v-slot="{ Component }">
    <transition name="fade" mode="out-in">
      <keep-alive :include="cachedViews">
        <component :is="Component" :key="$route.fullPath" />
      </keep-alive>
    </transition>
  </router-view>
</template>

常见问题

1. 如何在新窗口打开路由?

javascript
const route = router.resolve({ name: 'UserDetail', params: { id: 123 } })
window.open(route.href, '_blank')

2. 如何获取当前路由的所有匹配记录?

javascript
// matched 包含所有嵌套的路由记录
const matched = this.$route.matched

3. 如何判断路由是否激活?

javascript
// 使用 router-link 的 active-class
<router-link to="/user" active-class="active">用户</router-link>

// 或使用 $route 判断
computed: {
  isActive() {
    return this.$route.path === '/user'
  }
}

4. 如何监听路由参数变化?

javascript
watch: {
  '$route.params.id': {
    handler(newId, oldId) {
      this.fetchData(newId)
    },
    immediate: true
  }
}

API 参考

Router 实例方法

方法说明
push(location)导航到指定路由
replace(location)替换当前路由
go(n)前进或后退
back()后退
forward()前进
addRoute(route)添加路由
removeRoute(name)删除路由
getRoutes()获取所有路由
hasRoute(name)检查路由是否存在
resolve(location)解析路由位置

Route 对象属性

属性说明
path当前路径
params路由参数
query查询参数
hashURL hash
fullPath完整路径
name路由名称
matched匹配的路由记录
meta路由元信息
redirectedFrom重定向来源