一、Vite 项目里为啥要整合 Vue Router?
做 Vue 项目时,用 Vite 搭架构再常见不过,但路由管理咋和 Vue Router 结合?从项目初始化到复杂场景(动态路由、权限控制),再到部署踩坑,这篇文章把 Vite + Vue Router 的核心流程拆成问题逐个讲透,新手能跟着配,老手也能补细节~
单页应用(SPA)要做页面跳转、URL 管理,光靠组件切换可不够——用户刷新页面会回到首页、没法收藏特定页面、前进后退也乱套,Vue Router 是 Vue 生态官方路由工具,专门解决这些问题:它能管理 URL 和组件的映射关系,还能通过“路由守卫”控制权限、加载状态,让单页应用有像多页应用一样的路由体验。再看 Vite 和 Vue Router 的适配优势:Vite 主打“极速冷启动”,开发时改代码秒级热更新;Vue Router 4 对 Vue 3 做了深度优化(比如支持 Composition API、Tree - shaking 更彻底),两者结合后,开发阶段改路由配置能立刻看到效果,生产阶段路由懒加载能让首屏体积更小、加载更快,举个实际例子:用 Vite 搭的后台管理系统,加了 Vue Router 后,侧边栏切换子路由时页面不刷新,体验丝滑,还能通过路由守卫拦截未登录用户访问敏感页面。
怎么初始化一个带 Vue Router 的 Vite + Vue 项目?
想快速搭好基础环境,按这三步来:
用 Vite CLI 创建基础项目
打开终端,执行命令(选 Vue 模板):
npm create vite@latest my-vue-app -- --template vue
执行完后,进入项目目录并安装依赖:
cd my-vue-app npm install
安装 Vue Router
Vue 3 要搭配 Vue Router 4.x 版本,所以执行:
npm install vue-router@4
搭建路由相关目录结构
在 src
文件夹下新建 router
(放路由配置)、views
(放页面级组件)文件夹,最终目录大概长这样:
src/
├─ router/ # 路由核心配置
│ └─ index.js
├─ views/ # 页面组件(如首页、关于页)
│ ├─ HomeView.vue
│ └─ AboutView.vue
├─ components/ # 通用组件(如按钮、弹窗)
├─ App.vue # 根组件
└─ main.js # 项目入口
Vue Router 在 Vite 里的基础配置要注意啥?
核心是创建路由实例 + 挂载到 Vue 应用 + 渲染路由视图,分三步走:
写路由规则(src/router/index.js)
先引入 API 和组件,再定义路由映射关系:
import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' // 路由规则数组 const routes = [ { path: '/', // URL 路径(根路径) name: 'home', // 路由名称(可选,用于编程式导航) component: HomeView // 对应组件 }, { path: '/about', name: 'about', // 懒加载:访问时再加载组件,减少首屏体积(后面优化部分细讲) component: () => import('../views/AboutView.vue') } ] // 创建路由实例 const router = createRouter({ // history 模式:URL 更美观(如 /about),但部署要配服务端;hash 模式是 createWebHashHistory()(URL 带 #) history: createWebHistory(), routes // 传入路由规则 }) export default router
挂载路由到 Vue 应用(src/main.js)
把路由实例注入 Vue 应用,让所有组件能访问路由功能:
import { createApp } from 'vue' import App from './App.vue' import router from './router' // 引入路由配置 createApp(App) .use(router) // 挂载路由 .mount('#app')
用 和 渲染(App.vue)
<router-view>
是路由组件的“容器”,<router-link>
是导航链接(代替 <a>
避免页面刷新):
<template> <div> <!-- 导航栏 --> <nav> <router-link to="/">首页</router-link> | <router-link to="/about">lt;/router-link> </nav> <!-- 路由组件渲染区域 --> <router-view></router-view> </div> </template>
关键细节:createWebHistory
和 createWebHashHistory
咋选?
- 选
history
模式,URL 更简洁(如https://xxx.com/about
),但部署时服务器要配置“ fallback ”(否则刷新页面会 404); - 选
hash
模式,URL 带 (如https://xxx.com/#/about
),部署时不用改服务器配置,但 URL 美观度差一些。
动态路由在 Vite + Vue Router 里咋实现?
动态路由用来处理路径带参数的场景(比如商品详情页 /product/123
、用户页 /user/456
),核心是路由规则里定义参数 + 组件内获取参数。
配置动态路由规则
在 src/router/index.js
里加一条带参数的路由:
{ path: '/product/:id', // :id 是动态参数,冒号是固定语法 name: 'ProductDetail', component: () => import('../views/ProductDetail.vue') }
组件内获取参数
Vue 3 推荐用 组合式 API(useRoute
),也支持选项式 API(this.$route
)。
组合式 API 写法(ProductDetail.vue):
<template> <div>当前商品 ID:{{ productId }}</div> </template> <script setup> import { useRoute } from 'vue-router' // 引入路由 hooks const route = useRoute() const productId = route.params.id // 直接取动态参数 </script>
选项式 API 写法(老项目迁移用):
<template> <div>当前商品 ID:{{ $route.params.id }}</div> </template> <script> export default { computed: { productId() { return this.$route.params.id } } } </script>
动态路由的“坑”与解决
路由参数变化时(比如从 /product/1
跳转到 /product/2
),组件不会自动重新渲染!因为 Vue Router 认为“组件实例没销毁,只是参数变了”,解决方法有两种:
-
监听路由变化:用
watch
监听route.params
:import { watch } from 'vue' import { useRoute } from 'vue-router' const route = useRoute() watch( () => route.params.id, (newId) => { // newId 变化时执行逻辑(比如重新请求商品数据) fetchProduct(newId) } )
-
强制组件销毁重建:给
<router-view>
加key
,值为$route.fullPath
(URL 全路径):<router-view :key="$route.fullPath"></router-view>
嵌套路由怎么设计更合理?
嵌套路由适合页面布局有层级的场景(比如后台管理系统:顶部导航 + 侧边栏 + 内容区,内容区随侧边栏切换),核心是 children
配置 + 嵌套 <router-view>
。
规划布局与路由结构
以“仪表盘(Dashboard)”为例,结构分层:
- 父组件
DashboardLayout.vue
:包含侧边栏和内容区容器; - 子路由:
/dashboard
(默认显示DashboardHome
)、/dashboard/settings
(显示DashboardSettings
)。
配置嵌套路由规则(src/router/index.js)
{ path: '/dashboard', component: () => import('../views/DashboardLayout.vue'), // 父布局组件 children: [ { path: '', // 空路径表示“默认子路由” name: 'DashboardHome', component: () => import('../views/DashboardHome.vue') }, { path: 'settings', name: 'DashboardSettings', component: () => import('../views/DashboardSettings.vue') } ] }
父布局组件里渲染子路由(DashboardLayout.vue)
<template> <div class="dashboard-wrapper"> <!-- 侧边栏 --> <aside> <ul> <li><router-link to="/dashboard">首页</router-link></li> <li><router-link to="/dashboard/settings">设置</router-link></li> </ul> </aside> <!-- 子路由渲染区域 --> <main> <router-view></router-view> <!-- 子路由组件在这里显示 --> </main> </div> </template>
嵌套路由的优势
- 布局复用:父组件(如
DashboardLayout
)只渲染一次,子路由切换时,侧边栏、顶部导航这些固定布局不会重复渲染; - 路由层级清晰:URL 结构(
/dashboard/settings
)和组件层级一一对应,后续维护时,看 URL 就知道对应的组件结构。
路由守卫怎么用在实际业务里?
路由守卫是控制页面权限、处理跳转逻辑的关键工具,分全局守卫、路由独享守卫、组件内守卫三类。
全局前置守卫:登录拦截(最常用场景)
需求:访问需要登录的页面(如 /profile
)时,判断用户是否登录,没登录就跳转到登录页。
在 src/router/index.js
里配置:
router.beforeEach((to, from, next) => { // 假设用 localStorage 存 token 判断登录状态 const isLogin = !!localStorage.getItem('token') // 检查目标路由是否需要权限(通过 meta 字段标记) if (to.meta.requiresAuth && !isLogin) { next({ name: 'login' }) // 跳转到登录页 } else { next() // 放行 } })
然后在需要权限的路由里加 meta
标记:
{ path: '/profile', name: 'Profile', component: () => import('../views/Profile.vue'), meta: { requiresAuth: true } // 标记该页面需要登录 }
路由独享守卫:进入路由前做逻辑
需求:访问 /order
前,检查订单数据是否加载,没加载就先请求数据。
在路由规则里直接写 beforeEnter
:
{ path: '/order', name: 'Order', component: () => import('../views/Order.vue'), beforeEnter: (to, from, next) => { if (!window.orderData) { // 假设用 window 临时存数据(实际项目用状态管理更优) fetch('/api/order').then(res => res.json()).then(data => { window.orderData = data next() // 数据加载完,放行 }) } else { next() // 数据已存在,直接放行 } } }
组件内守卫:组件生命周期结合路由
需求:用户离开表单页面时,提示“是否放弃未保存内容”。
用 onBeforeRouteLeave
(组合式 API):
<template> <form>...</form> </template> <script setup> import { onBeforeRouteLeave } from 'vue-router' onBeforeRouteLeave((to, from, next) => { if (window.confirm('当前表单未保存,确定离开?')) { next() // 确认离开,放行 } else { next(false) // 取消离开,留在当前页 } }) </script>
路由守卫的核心逻辑
to
:目标路由对象(要跳转到哪个页面);from
:当前路由对象(从哪个页面跳过来);next
:必须调用的“放行函数”——调用next()
放行,next(false)
取消跳转,next({ name: 'xxx' })
跳转到指定页面。
怎么通过路由懒加载优化 Vite 项目性能?
Vite 天生支持动态 import(),配合 Vue Router 的懒加载配置,能让首屏只加载“首页”等核心代码,其他页面按需加载,大幅减少首屏加载时间。
路由懒加载的原理
传统打包方式把所有页面组件塞到一个 JS 文件里,首屏要加载全部代码,体积大、速度慢,懒加载是把每个页面组件打成单独的代码块,访问对应路由时再加载,实现“按需加载”。
Vite 里的路由懒加载配置
在路由规则里,把 component
写成动态 import 函数即可:
{ path: '/about', name: 'about', // 动态 import,Vite 会自动处理代码分割 component: () => import('../views/AboutView.vue') }
进阶:分组懒加载 + 加载状态
-
分组懒加载:把多个相关组件打包到一个代码块里(减少请求次数),比如后台管理的子页面:
// 把 Dashboard 相关组件打到同一个 chunk const DashboardRoutes = () => import('../views/dashboard/Index.vue') const DashboardHome = () => import('../views/dashboard/Home.vue') const DashboardSettings = () => import('../views/dashboard/Settings.vue') // 路由配置 { path: '/dashboard', component: DashboardRoutes, children: [ { path: '', component: DashboardHome }, { path: 'settings', component: DashboardSettings } ] }
-
加载状态提示:用户访问懒加载页面时,可能看到“白屏”,所以要加加载中提示,用全局路由守卫 + 状态管理实现:
在src/router/index.js
里:let loading = false router.beforeEach((to, from, next) => { loading = true // 路由开始加载,标记为 true next() }) router.afterEach(() => { loading = false // 路由加载完成,标记为 false })
在
App.vue
里显示加载状态:<template> <div v-if="loading" class="loading-mask">加载中...</div> <nav>...</nav> <router-view></router-view> </template> <script setup> import { ref } from 'vue' import { useRouter } from 'vue-router' const router = useRouter() const loading = ref(false) router.beforeEach(() => { loading.value = true }) router.afterEach(() => { loading.value = false }) </script>
部署 Vite + Vue Router 项目时路由有啥坑?
部署后最容易踩的是 路由模式不兼容,导致页面刷新 404,核心是 history
模式和 hash
模式的区别。
Hash 模式(createWebHashHistory)
URL 格式:http://xxx.com/#/about
,路由信息存在 后面。部署时不需要服务端配置——因为浏览器只会把 前面的路径发给服务器,服务器返回 index.html
后,前端路由自己解析 后的内容。
- 优点:部署简单,兼容老服务器;
- 缺点:URL 带 ,美观度差。
History 模式(createWebHistory)
URL 格式:http://xxx.com/about
,更像传统网站 URL,但服务器必须配置 fallback——当用户直接访问 /about
时,服务器要返回 index.html
(否则会报 404,因为服务器没有 /about
这个物理文件)。
不同平台的配置方式:
-
Vercel/Netlify:自动支持 SPA fallback,部署时不用额外配置;
-
Nginx:在配置文件里加
try_files
:server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; # 项目打包后的 dist 目录 index index.html; location / { try_files $uri $uri/ /
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。