一、基础入门,vue-router4 咋搭起基本路由?
不少刚开始用Vue3做项目的同学,一碰到vue-router4就犯懵:路由配置咋和以前不一样了?动态路由、嵌套路由咋整?路由守卫写法变在哪?这篇文章用问答形式,把vue-router4从基础到进阶的关键问题拆明白,帮你少走弯路。
问题1:vue-router4 和 v3 最核心的区别是啥?
最大的变化是API 设计和 Vue3 生态的适配,以前 v3 是通过 new VueRouter({...})
创建路由实例,v4 要用 createRouter
这种函数式的写法;路由模式也变了,v3 里的 mode: 'history'
现在得换成 createWebHistory()
(对应 history 模式)或者 createWebHashHistory()
(对应 hash 模式),Vue3 主推组合式 API,v4 也新增了 useRouter
、useRoute
这些组合式 API 钩子,在 setup 里用路由更顺手。
举个最直观的配置对比:
v3 写法:
import VueRouter from 'vue-router' Vue.use(VueRouter) const router = new VueRouter({ mode: 'history', routes: [...] })
v4 写法:
import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), // 替代原来的mode routes: [...] }) // 不需要Vue.use了,直接在createApp里用app.use(router)
问题2:vue-router4 最基础的路由配置步骤是啥?
分四步就能跑通基础路由:
- 安装:命令行执行
npm i vue-router@4
(注意版本,v4 才适配 Vue3)。 - 写路由规则:新建
router/index.js
,用createRouter
+createWebHistory
+routes
数组定义页面映射。import { createRouter, createWebHistory } from 'vue-router' import Home from '../views/Home.vue' import About from '../views/About.vue'
const routes = [ { path: '/', component: Home }, { path: '/about', component: About } ]
const router = createRouter({ history: createWebHistory(), routes })
export default router
**注入路由到Vue应用**:在 `main.js` 里把路由实例传给 `app.use()`:
```js
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>
(导航链接):<template> <div> <router-link to="/">首页</router-link> <router-link to="/about">lt;/router-link> <router-view></router-view> </div> </template>
这样一配,点击导航就能切换页面啦~
问题3:<router-link>
用法和之前有啥不同?
v4 里 <router-link>
做了不少简化和增强:
-
以前的
tag
属性没了,想自定义标签得用custom
属性 + 插槽,比如想把router-link
改成按钮:<router-link to="/" custom v-slot="{ navigate }"> <button @click="navigate">首页</button> </router-link>
-
to
属性更灵活,支持对象形式,比如跳转到带查询参数的页面:<router-link :to="{ path: '/user', query: { id: 123 }}">用户页</router-link>
这样 URL 会变成
/user?id=123
。 -
激活样式的类名,默认还是
router-link-active
,但现在可以通过active-class
或者全局配置linkActiveClass
自定义,比以前更灵活。
核心功能:动态路由、嵌套路由咋玩?
问题1:动态路由(比如用户ID页面)咋配置?
动态路由就是 URL 里带可变参数(比如用户ID),配置时在路由规则里用 标记参数,组件里用 useRoute
取参数。
举个用户详情页的例子:
-
配置路由规则:在
routes
数组里加一条:{ path: '/user/:id', // :id 是动态参数 component: () => import('../views/User.vue') // 这里用了懒加载,后面讲 }
-
组件内取参数:在
User.vue
的 setup 里用useRoute
:import { useRoute } from 'vue-router'
export default { setup() { const route = useRoute() // route.params.id URL 里的动态参数 console.log('当前用户ID:', route.params.id) return {} } }
3. **跳转动态路由**:用 `router.push` 时,可以传对象形式:
```js
import { useRouter } from 'vue-router'
setup() {
const router = useRouter()
// 跳转到用户ID为100的页面
const goToUser = () => {
router.push({ path: '/user/100' })
// 或者用命名路由(如果配了name的话):router.push({ name: 'user', params: { id: 100 } })
}
return { goToUser }
}
这样不管是从导航链接点进来,还是编程式跳转,都能拿到动态参数~
问题2:嵌套路由(子路由)该怎么设置?
嵌套路由常见于布局型页面,比如后台管理系统的“布局页”里嵌套“仪表盘”“设置”等子页面,关键是父路由里配 children
数组,并且父组件里要有 <router-view>
来显示子路由。
举个后台布局的例子:
-
配置路由规则:父路由
/admin
下有两个子路由dashboard
和settings
:const routes = [ { path: '/admin', component: AdminLayout, // 父布局组件 children: [ { path: '', redirect: 'dashboard' }, // 默认跳转到dashboard { path: 'dashboard', component: Dashboard }, { path: 'settings', component: Settings } ] } ]
注意子路由的
path
不加 ,会自动拼接父路由的path
,所以访问/admin/dashboard
就能匹配到 Dashboard 组件。 -
父组件里放
<router-view>
:在AdminLayout.vue
里,除了侧边栏、顶栏,还要用<router-view>
显示子页面:<template> <div class="admin-layout"> <aside>侧边栏导航</aside> <main> <router-view></router-view> <!-- 子路由组件显示在这 --> </main> </div> </template>
-
子路由的导航:可以在父组件侧边栏用
<router-link>
跳转子路由:<aside> <router-link to="dashboard">仪表盘</router-link> <router-link to="settings">设置</router-link> </aside>
这里
to
用相对路径(不加 ),会自动基于父路由/admin
拼接。
这样嵌套结构就清晰啦,父布局负责框架,子路由负责具体页面内容~
问题3:路由懒加载在 vue-router4 里咋实现?
路由懒加载能让页面按需加载,减少首屏体积,v4 里实现特简单,把组件导入改成 () => import('组件路径')
这种动态导入形式,Webpack 之类的打包工具会自动拆分代码块。
对比一下普通导入和懒加载:
普通导入(首屏就加载所有组件):
import Home from '../views/Home.vue' import About from '../views/About.vue' const routes = [ { path: '/', component: Home }, { path: '/about', component: About } ]
懒加载(访问对应路由时才加载组件):
const routes = [ { path: '/', component: () => import('../views/Home.vue') }, { path: '/about', component: () => import('../views/About.vue') } ]
甚至还能给懒加载的组件取别名,方便调试:
{ path: '/about', component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') }
这样打包后,About 组件会单独生成一个 about.[hash].js
的文件,访问 /about
时才会加载,首屏加载速度就上去啦~
路由守卫:导航控制咋搞?
问题1:全局守卫、组件内守卫在 v4 里咋用?
路由守卫用来控制导航流程(比如登录验证、权限判断),v4 里分全局守卫和组件内守卫,写法和 v3 有变化,尤其是组件内守卫在组合式 API 里的用法。
全局守卫 直接在路由实例上挂钩子:
-
router.beforeEach
:导航触发时最先执行,常用作登录拦截。router.beforeEach((to, from, next) => { // to: 要去的路由;from: 来自的路由;next: 放行函数(v4 里也支持 Promise 风格,next 可以省略,return 布尔值或路由对象) if (to.meta.requiresAuth && !isLogin()) { next('/login') // 没登录且需要权限,跳登录页 } else { next() // 放行 } })
-
router.beforeResolve
:和 beforeEach 类似,但在组件内守卫之后、导航确认之前执行,适合做全局解析逻辑。 -
router.afterEach
:导航完成后执行,常用作修改页面标题、埋点统计等。router.afterEach((to, from) => { document.title = to.meta.title || '默认标题' })
组件内守卫 在 v4 里分两种场景:
-
选项式 API 里,还是用
beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
,用法和 v3 差不多。 -
组合式 API(setup 里),得用
onBeforeRouteEnter
、onBeforeRouteUpdate
、onBeforeRouteLeave
这些钩子(从vue-router
导入),比如在 setup 里处理离开守卫:import { onBeforeRouteLeave } from 'vue-router'
setup() { onBeforeRouteLeave((to, from) => { // 离开当前路由时的逻辑,比如提示“是否保存” return window.confirm('确定离开吗?未保存内容会丢失~') }) }
注意 `onBeforeRouteEnter` 执行时,组件实例还没创建,所以不能访问 `this`,如果要拿组件数据,得用回调里的 `next(vm => { ... })` 形式(但组合式 API 里更推荐用 `onMounted` 配合 `useRoute`)。
#### 问题2:导航被取消(比如重复点同一路由)咋处理?
v4 里导航是 **Promise 风格** 的,重复点击同一路由会触发 `AbortError`,比如用 `router.push` 时,如果导航被中断(比如守卫里阻止了),会返回一个被拒绝的 Promise。
处理方式有两种:
1. **try...catch 捕获错误**:在编程式导航时用 try...catch:
```js
import { useRouter } from 'vue-router'
import { isNavigationFailure, NavigationFailureType } from 'vue-router'
setup() {
const router = useRouter()
const goToPage = async () => {
try {
await router.push('/same-path')
} catch (error) {
if (isNavigationFailure(error, NavigationFailureType.aborted)) {
// 导航被取消,这里可以提示用户
console.log('导航被取消啦~')
}
}
}
return { goToPage }
}
- 全局处理错误:用
router.onError
捕获所有路由错误:router.onError((error) => { console.log('路由错误:', error) // 可以统一跳转到错误页 router.push('/error') })
这样就能优雅处理重复导航、守卫拦截等导致的导航失败啦~
问题3:路由元信息(meta)咋用?比如设置页面标题、权限?
路由元信息(meta
)是给路由附加自定义信息的字段,比如页面标题、是否需要登录,配置和使用都很简单:
-
配置 meta:在路由规则里加
meta
对象:const routes = [ { path: '/admin', component: Admin, meta: { requiresAuth: true, // 需要登录 title: '管理后台' // 页面标题 } }, { path: '/public', component: Public, meta: { requiresAuth: false, title: '公共页面' } } ]
-
读取 meta:在全局守卫或组件内用
to.meta
(全局守卫里)或useRoute().meta
(组件内),比如全局守卫里判断权限:router.beforeEach((to, from) => { if (to.meta.requiresAuth && !isLogin()) { return '/login' // 没登录且需要权限,跳登录页 } })
再比如组件内设置页面标题:
import { useRoute, onMounted } from 'vue-router'
setup() { const route = useRoute() onMounted(() => { document.title = route.meta.title || '默认标题' }) }
meta 就像路由的“小背包”,想塞啥自定义信息都能塞,灵活得很~
### 四、进阶与踩坑:生产部署、状态管理咋配合?
#### 问题1:vue-router4 怎么和 Pinia(或 Vuex)配合做权限控制?
权限控制核心思路是:**在全局守卫里,用状态管理工具(Pinia/Vuex)里的用户信息,判断是否能进入目标路由**,以 Pinia 为例:
1. **在 Pinia 里存用户权限**:比如有个 `userStore`,里面有 `userInfo.role` 表示用户角色。
2. **全局守卫里做判断**:在 `router.beforeEach` 里导入 store,检查目标路由 meta 里的权限要求:
```js
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '../stores/user'
const router = createRouter({...})
router.beforeEach((to, from) => {
const userStore = useUserStore()
// 假设路由meta里有 role 字段,代表需要的角色
if (to.meta.role && userStore.userInfo.role !== to.meta.role) {
// 角色不匹配,跳403页面
return '/403'
}
// 其他逻辑...
})
- 路由规则里配 meta.role:比如管理员页面需要
admin
角色:{ path: '/admin', component: Admin, meta: { role: 'admin' } }
这样结合状态管理和路由 meta,就能实现细粒度的权限控制~如果用 Vuex,思路一样,只是取状态的方式换成 store.state.user.role
就行。
问题2:路由跳转时传参(query和params)有啥区别?
路由传参分 query(查询参数)和 params(路由参数),核心区别是是否在 URL 显式体现、刷新后是否保留:
-
query:
- 会拼在 URL 后面,格式是
?key=value
(/user?name=张三
)。 - 刷新页面后参数还在,因为存在 URL 里。
- 配置路由时不需要在
path
里写,任何路由都能传。 - 取值用
useRoute().query.name
。
- 会拼在 URL 后面,格式是
-
params:
- 有两种情况:动态路由参数(在
path
里用:id
定义)和 非动态路由的 params(路由规则里没定义,靠命名路由传)。 - 动态路由参数会在 URL 里(
/user/123
),刷新保留;非动态路由的 params 不在 URL 里,刷新就丢了。 - 动态路由参数取值用
useRoute().params.id
;非动态的得用命名路由传参(router.push({ name: 'user', params: { id: 123 } })
),但这种方式不推荐,
- 有两种情况:动态路由参数(在
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。