Skip to content

Vue Router ​

路由概念 ​

前端路由通过 URL 中的 hash(#)或 HTML5 History 实现单页应用(SPA)的页面切换,无需重新加载页面。

Vue2 路由 ​

安装与配置 ​

js
// src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/views/Home.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',       // 路由模式:hash / history / abstract
  base: '/',             // 基础路径
  linkActiveClass: 'active',   // 路由匹配时的样式类
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/about',
      name: 'About',
      component: () => import('@/views/About.vue') // 路由懒加载
    }
  ]
})
// src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/views/Home.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',       // 路由模式:hash / history / abstract
  base: '/',             // 基础路径
  linkActiveClass: 'active',   // 路由匹配时的样式类
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/about',
      name: 'About',
      component: () => import('@/views/About.vue') // 路由懒加载
    }
  ]
})
js
// main.js
import router from './router'

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')
// main.js
import router from './router'

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

路由显示 ​

vue
<template>
  <div id="app">
    <!-- 导航链接 -->
    <router-link to="/">首页</router-link>
    <router-link :to="{ name: 'About' }">关于</router-link>
    <router-link to="/user/123" active-class="active">用户</router-link>

    <!-- 路由出口 -->
    <router-view />
  </div>
</template>
<template>
  <div id="app">
    <!-- 导航链接 -->
    <router-link to="/">首页</router-link>
    <router-link :to="{ name: 'About' }">关于</router-link>
    <router-link to="/user/123" active-class="active">用户</router-link>

    <!-- 路由出口 -->
    <router-view />
  </div>
</template>

动态路由匹配 ​

js
const routes = [
  // 动态参数
  { path: '/user/:id', component: User },
  // 可选参数(加 ?)
  { path: '/shop/:type/:id?', component: Shop }
]
const routes = [
  // 动态参数
  { path: '/user/:id', component: User },
  // 可选参数(加 ?)
  { path: '/shop/:type/:id?', component: Shop }
]
vue
<template>
  <div>
    <!-- 通过 $route.params 获取参数 -->
    <p>用户ID:{{ $route.params.id }}</p>
    <p>查询参数:{{ $route.query.page }}</p>
  </div>
</template>
<template>
  <div>
    <!-- 通过 $route.params 获取参数 -->
    <p>用户ID:{{ $route.params.id }}</p>
    <p>查询参数:{{ $route.query.page }}</p>
  </div>
</template>
vue
<template>
  <!-- 传参方式 -->
  <router-link :to="{ name: 'User', params: { id: 123 } }">用户</router-link>
  <router-link :to="{ path: '/user', query: { id: 123 } }">用户</router-link>
</template>
<template>
  <!-- 传参方式 -->
  <router-link :to="{ name: 'User', params: { id: 123 } }">用户</router-link>
  <router-link :to="{ path: '/user', query: { id: 123 } }">用户</router-link>
</template>

嵌套路由 ​

js
const routes = [
  {
    path: '/account',
    component: Account,
    redirect: '/account/login', // 重定向
    children: [
      { path: 'login', component: Login },    // 访问 /account/login
      { path: 'register', component: Register } // 访问 /account/register
      // 注意子路由 path 不加 /
    ]
  }
]
const routes = [
  {
    path: '/account',
    component: Account,
    redirect: '/account/login', // 重定向
    children: [
      { path: 'login', component: Login },    // 访问 /account/login
      { path: 'register', component: Register } // 访问 /account/register
      // 注意子路由 path 不加 /
    ]
  }
]
vue
<template>
  <!-- Account.vue — 父组件需要包含 router-view -->
  <div>
    <h2>账户管理</h2>
    <router-view />
  </div>
</template>
<template>
  <!-- Account.vue — 父组件需要包含 router-view -->
  <div>
    <h2>账户管理</h2>
    <router-view />
  </div>
</template>

编程式导航 ​

js
// 跳转(带历史记录)
this.$router.push('/home')
this.$router.push({ name: 'User', params: { id: 123 } })
this.$router.push({ path: '/user', query: { page: 1 } })

// 替换(不带历史记录)
this.$router.replace('/home')

// 前进/后退
this.$router.go(1)   // 前进
this.$router.go(-1)  // 后退
// 跳转(带历史记录)
this.$router.push('/home')
this.$router.push({ name: 'User', params: { id: 123 } })
this.$router.push({ path: '/user', query: { page: 1 } })

// 替换(不带历史记录)
this.$router.replace('/home')

// 前进/后退
this.$router.go(1)   // 前进
this.$router.go(-1)  // 后退

重复点击路由报错处理(Vue2):

js
// router/index.js
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
  return originalPush.call(this, location).catch(err => err)
}
// router/index.js
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
  return originalPush.call(this, location).catch(err => err)
}

命名视图 ​

vue
<template>
  <!-- 在一个路由下显示多个组件 -->
  <router-view name="sidebar" />
  <router-view name="main" />
  <router-view />  <!-- 默认:name="default" -->
</template>
<template>
  <!-- 在一个路由下显示多个组件 -->
  <router-view name="sidebar" />
  <router-view name="main" />
  <router-view />  <!-- 默认:name="default" -->
</template>
js
const routes = [
  {
    path: '/dashboard',
    components: {
      default: Dashboard,
      sidebar: Sidebar,
      main: MainContent
    }
  }
]
const routes = [
  {
    path: '/dashboard',
    components: {
      default: Dashboard,
      sidebar: Sidebar,
      main: MainContent
    }
  }
]

路由守卫 ​

全局前置守卫 ​

js
const router = new Router({ routes })

router.beforeEach((to, from, next) => {
  // to — 目标路由对象
  // from — 当前路由对象
  // next() — 放行
  // next(false) — 中断导航
  // next('/login') — 重定向
  // next({ name: 'Login' }) — 重定向

  const token = localStorage.getItem('token')
  if (to.meta.requiresAuth && !token) {
    next({ name: 'Login' })
  } else {
    next()
  }
})
const router = new Router({ routes })

router.beforeEach((to, from, next) => {
  // to — 目标路由对象
  // from — 当前路由对象
  // next() — 放行
  // next(false) — 中断导航
  // next('/login') — 重定向
  // next({ name: 'Login' }) — 重定向

  const token = localStorage.getItem('token')
  if (to.meta.requiresAuth && !token) {
    next({ name: 'Login' })
  } else {
    next()
  }
})

全局后置守卫 ​

js
router.afterEach((to, from) => {
  // 跳转后操作,没有 next
  document.title = to.meta.title || '默认标题'
})
router.afterEach((to, from) => {
  // 跳转后操作,没有 next
  document.title = to.meta.title || '默认标题'
})

路由独享守卫 ​

js
const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter(to, from, next) {
      if (localStorage.getItem('role') !== 'admin') {
        next('/')
      } else {
        next()
      }
    }
  }
]
const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter(to, from, next) {
      if (localStorage.getItem('role') !== 'admin') {
        next('/')
      } else {
        next()
      }
    }
  }
]

组件内守卫 ​

js
export default {
  beforeRouteEnter(to, from, next) {
    // 进入组件前,不能访问 this(组件未创建)
    // 可以通过回调访问实例
    next(vm => {
      vm.loadData()
    })
  },
  beforeRouteUpdate(to, from, next) {
    // 路由参数变化时(如 /user/1 → /user/2)
    // 可以访问 this
    this.fetchUser(to.params.id)
    next()
  },
  beforeRouteLeave(to, from, next) {
    // 离开组件前
    const answer = window.confirm('确认离开?')
    if (answer) next()
    else next(false)
  }
}
export default {
  beforeRouteEnter(to, from, next) {
    // 进入组件前,不能访问 this(组件未创建)
    // 可以通过回调访问实例
    next(vm => {
      vm.loadData()
    })
  },
  beforeRouteUpdate(to, from, next) {
    // 路由参数变化时(如 /user/1 → /user/2)
    // 可以访问 this
    this.fetchUser(to.params.id)
    next()
  },
  beforeRouteLeave(to, from, next) {
    // 离开组件前
    const answer = window.confirm('确认离开?')
    if (answer) next()
    else next(false)
  }
}

路由元信息 ​

js
const routes = [
  {
    path: '/admin',
    component: Admin,
    meta: {
      title: '管理后台',
      requiresAuth: true,
      roles: ['admin', 'editor']
    }
  }
]
const routes = [
  {
    path: '/admin',
    component: Admin,
    meta: {
      title: '管理后台',
      requiresAuth: true,
      roles: ['admin', 'editor']
    }
  }
]

动态路由 ​

js
// router.addRoutes — Vue2.2+ 新增动态路由
router.addRoutes([
  { path: '/new', component: NewPage }
])
// router.addRoutes — Vue2.2+ 新增动态路由
router.addRoutes([
  { path: '/new', component: NewPage }
])

滚动行为 ​

js
const router = new Router({
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition // 前进/后退时保持滚动位置
    }
    if (to.hash) {
      return { selector: to.hash } // 滚动到锚点
    }
    return { x: 0, y: 0 } // 滚动到顶部
  }
})
const router = new Router({
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition // 前进/后退时保持滚动位置
    }
    if (to.hash) {
      return { selector: to.hash } // 滚动到锚点
    }
    return { x: 0, y: 0 } // 滚动到顶部
  }
})

Vue3 路由 ​

安装与配置 ​

js
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About.vue'),
    meta: { title: '关于', requiresAuth: true }
  }
]

const router = createRouter({
  history: createWebHistory(),  // createWebHashHistory / createMemoryHistory
  routes
})

export default router
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About.vue'),
    meta: { title: '关于', requiresAuth: true }
  }
]

const router = createRouter({
  history: createWebHistory(),  // createWebHashHistory / createMemoryHistory
  routes
})

export default router
js
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')

组合式 API — useRouter / useRoute ​

vue
<script setup>
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

// 访问参数
console.log(route.params.id)
console.log(route.query.page)

// 编程式导航
function goHome() {
  router.push({ name: 'Home' })
}

function goUser(id) {
  router.push({ name: 'User', params: { id } })
}

function goBack() {
  router.go(-1)
}
</script>
<script setup>
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

// 访问参数
console.log(route.params.id)
console.log(route.query.page)

// 编程式导航
function goHome() {
  router.push({ name: 'Home' })
}

function goUser(id) {
  router.push({ name: 'User', params: { id } })
}

function goBack() {
  router.go(-1)
}
</script>

组合式导航守卫 ​

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

// 离开守卫
onBeforeRouteLeave((to, from) => {
  const answer = window.confirm('确认离开?')
  if (!answer) return false
})

// 路由参数更新守卫
onBeforeRouteUpdate((to, from) => {
  fetchUser(to.params.id)
})
</script>
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

// 离开守卫
onBeforeRouteLeave((to, from) => {
  const answer = window.confirm('确认离开?')
  if (!answer) return false
})

// 路由参数更新守卫
onBeforeRouteUpdate((to, from) => {
  fetchUser(to.params.id)
})
</script>

路由自动引入 ​

js
// 使用 require.context 自动注册路由(Vue2 Webpack)
const routerContext = require.context('./', true, /index\.js$/)
routerContext.keys().forEach(route => {
  if (route.startsWith('./index')) return
  const routerModule = routerContext(route)
  routes = [...routes, ...(routerModule.default || routerModule)]
})
// 使用 require.context 自动注册路由(Vue2 Webpack)
const routerContext = require.context('./', true, /index\.js$/)
routerContext.keys().forEach(route => {
  if (route.startsWith('./index')) return
  const routerModule = routerContext(route)
  routes = [...routes, ...(routerModule.default || routerModule)]
})

Vue2 vs Vue3 路由对比 ​

特性Vue2 (vue-router 3.x)Vue3 (vue-router 4.x)
创建方式new Router()createRouter()
历史模式mode: 'history'history: createWebHistory()
获取路由this.$router / this.$routeuseRouter() / useRoute()
组件内守卫beforeRouteEnter 等onBeforeRouteLeave / onBeforeRouteUpdate
动态路由router.addRoutes()router.addRoute()
应用挂载Vue.use(Router)app.use(router)