Vue Router 从0到会用:手摸手带你搞懂前端路由,每行代码都有注释
一、没有路由之前,页面跳转是怎样的?
先回忆一下最原始的多页面网站是怎么跳转的:
你点了导航栏里的“关于我们”,浏览器就向服务器发一个请求,服务器返回一个全新的HTML页面,整个页面都刷新了。白屏一下,再加载,体验很差。
后来有了单页应用(SPA):整个网站其实只有一个HTML页面,所有“换页面”的效果,本质上是JavaScript在偷偷地切换显示的组件,并没有真正跳转到新页面。
而Vue Router就是Vue官方提供的、专门管这个“组件切换”的工具。
二、先搭一个能跑的Vue项目
如果你还没创建过Vue项目,跟着做:
bash
# 1. 创建项目,项目名随便取,这里叫 my-router-demo npm create vue@latest my-router-demo # 2. 进入项目文件夹 cd my-router-demo # 3. 安装依赖 npm install # 4. 启动开发服务器 npm run dev
注意:创建过程中会问你一堆要不要装的东西。看到 Router 那项,选 Yes,其他的可以先不管。这样创建出来的项目已经自带路由了,省去手动装的步骤。
如果创建时忘了勾Router,就手动装一下:
bash
npm install vue-router@4
三、路由到底在干什么?一张图看懂
在你浏览器地址栏里输一个地址,比如/home,Vue Router就去找一张“对照表”,看/home应该显示哪个组件,然后把那个组件渲染到指定位置。
text
浏览器地址栏: /home ——> 显示 Home组件 浏览器地址栏: /about ——> 显示 About组件 浏览器地址栏: /user/123 ——> 显示 User组件,并且能拿到123这个参数
这张“对照表”就是路由配置,也就是路由表。
四、最小化例子:两个页面的路由
先看项目里创建时自动生成的目录结构(只关注几个重要的):
text
src/ ├── router/ │ └── index.js ← 路由配置文件,路由表在这 ├── views/ │ ├── HomeView.vue ← 首页组件 │ └── AboutView.vue ← 关于页组件 ├── App.vue ← 根组件 └── main.js ← 入口文件
1. 路由配置文件src/router/index.js长这样
javascript
// 第一步:从vue-router包里引入需要的函数 // createRouter: 创建路由实例 // createWebHistory: 使用HTML5的History模式(地址栏没有#号,更像普通网页) import { createRouter, createWebHistory } from 'vue-router' // 第二步:引入要用到的组件(这些就是不同“页面”) // @ 是src目录的别名,写 @/views/xxx 等于写 src/views/xxx import HomeView from '@/views/HomeView.vue' import AboutView from '@/views/AboutView.vue' // 第三步:定义路由表(就是那张“对照表”) // 每条路由记录是一个对象,包含path(路径)和component(对应的组件) const routes = [ { path: '/', // 访问根路径 / 时 name: 'home', // 给这条路由起个名字,方便后面用 component: HomeView // 显示HomeView这个组件 }, { path: '/about', // 访问 /about 时 name: 'about', // 名字 component: AboutView // 显示AboutView组件 } ] // 第四步:创建路由实例 const router = createRouter({ history: createWebHistory(), // 用History模式,地址栏干净 routes: routes // 把刚才定义的路由表传进去 }) // 第五步:导出路由实例,给main.js用 export default router2. 入口文件src/main.js里挂载路由
javascript
import { createApp } from 'vue' import App from './App.vue' import router from './router' // 引入刚才写的路由文件 const app = createApp(App) // 这一行很关键:把路由实例挂到Vue应用上 // 这样所有组件里都能用路由的功能 app.use(router) app.mount('#app') // 挂载到index.html里的#app元素上3. 根组件App.vue里指定“出口”
vue
<template> <div id="app"> <!-- 导航栏,点击切换路由 --> <nav> <!-- <router-link> 是Vue Router提供的组件,用来生成导航链接 to属性指定目标路径,点击后地址栏变成对应的path 浏览器里它会被渲染成一个<a>标签 --> <router-link to="/">首页</router-link> <router-link to="/about">关于</router-link> </nav> <!-- 这是最重要的东西:router-view 它就像一个占位符,当前匹配到的路由组件就渲染在这里 访问/时,这里渲染HomeView 访问/about时,这里渲染AboutView 其他内容(比如上面的nav)不会变,只有这里会切换 --> <router-view /> </div> </template>
到这里,你已经有了两个“页面”的应用。在浏览器里点首页和关于,你会发现地址栏变了,但页面没有整体刷新,只有<router-view />那块区域在切换。这就是单页应用的感觉。
五、动态路由:同一个组件,根据参数显示不同内容
比如用户详情页,/user/1显示用户1的信息,/user/2显示用户2的信息。用的是同一个组件,只是参数不同。
1. 路由配置加个动态参数
javascript
// router/index.js 里加一条 const routes = [ // ... 前面的路由 ... { // :id 表示这部分是动态的,会匹配 /user/1、/user/2、/user/abc 等 // 冒号后面的id是你给这个参数起的名字 path: '/user/:id', name: 'user', component: () => import('@/views/UserView.vue') // 上面用的是懒加载:只有访问到这个路由时,才去下载组件代码 // import()返回一个Promise,Vue Router会自动处理 } ]2. UserView.vue 里获取动态参数
vue
<template> <div> <!-- 通过 $route.params 获取路径里的参数 $route 是当前路由的信息对象,Vue Router自动提供 .params 是一个对象,里面包含所有动态参数 我们在路由里定义了 :id,所以这里用 $route.params.id 取值 --> <h1>用户详情页</h1> <p>当前用户ID:{{ $route.params.id }}</p> </div> </template> <script setup> // 在script里想获取路由参数,用 useRoute 这个函数 import { useRoute } from 'vue-router' // useRoute() 返回当前路由的信息对象,等同于模板里的 $route const route = useRoute() // 打印一下看看里面有什么 console.log('当前路径参数:', route.params) console.log('用户ID:', route.params.id) // route.params.id 就是路径里 :id 匹配到的值 </script>试试:浏览器访问/user/123,页面上显示ID是123;改成/user/456,显示ID变成456。组件是同一个,数据跟着URL变。
六、编程式导航:用代码跳转,不用router-link
有时候你不能用<router-link>,比如填完表单点提交,验证通过后再跳转。这就要在JS代码里控制跳转。
vue
<template> <div> <h2>登录页</h2> <button @click="goToHome">登录成功,去首页</button> <button @click="goToUser">查看用户123</button> <button @click="goBack">返回上一页</button> </div> </template> <script setup> // useRouter 用来拿到路由实例,用它进行跳转 import { useRouter } from 'vue-router' const router = useRouter() // 拿到路由实例 function goToHome() { // push方法:跳转到指定路径,会留下历史记录(可以点浏览器后退) // 参数可以是路径字符串 router.push('/') } function goToUser() { // 也可以传一个对象,用name指定路由名,用params传参 // 这种方式更灵活,路径变了代码不用改 router.push({ name: 'user', // 跳转到name为'user'的路由 params: { id: '123' } // 传参数,对应路由里定义的 :id }) } function goBack() { // back方法:返回上一页,等同于浏览器的后退按钮 router.back() } </script>push和replace的区别:
push('/home'):跳转后会留下历史记录,用户能点返回。replace('/home'):跳转后替换当前历史记录,用户点返回回不来。适合登录成功后跳转,不让用户退回登录页。
七、嵌套路由:页面里面还有子页面
很多后台管理系统是这样的布局:
左边侧边栏不变
顶部导航不变
中间内容区随着菜单点击而切换
这就是嵌套路由的典型场景。
1. 路由配置写 children
javascript
// router/index.js const routes = [ { path: '/admin', component: () => import('@/views/AdminLayout.vue'), // children定义子路由,匹配 /admin/xxx 的路径 children: [ { path: '', // 空字符串,访问 /admin 时默认显示 name: 'admin-dashboard', component: () => import('@/views/admin/Dashboard.vue') }, { path: 'users', // 注意不用写斜杠,实际匹配 /admin/users name: 'admin-users', component: () => import('@/views/admin/UserList.vue') }, { path: 'settings', // 匹配 /admin/settings name: 'admin-settings', component: () => import('@/views/admin/Settings.vue') } ] } ]2. AdminLayout.vue 里面再放一个router-view
vue
<template> <div class="admin-layout"> <!-- 侧边栏,导航到子路由 --> <aside class="sidebar"> <h3>后台管理</h3> <ul> <li><router-link to="/admin">仪表盘</router-link></li> <li><router-link to="/admin/users">用户管理</router-link></li> <li><router-link to="/admin/settings">系统设置</router-link></li> </ul> </aside> <!-- 右侧内容区 --> <main class="content"> <!-- 第二个router-view,用来显示子路由对应的组件 /admin 时显示 Dashboard /admin/users 时显示 UserList 以此类推 --> <router-view /> </main> </div> </template>
嵌套路由的逻辑就是:父组件里放一个<router-view />,子路由匹配到的组件就渲染在那个位置。
八、路由守卫:在跳转前做点什么
比如有些页面必须登录后才能访问,没登录就跳到登录页。这叫路由守卫。
全局前置守卫:所有路由跳转前都会触发
javascript
// router/index.js 里,创建router实例之后加 router.beforeEach((to, from, next) => { // 三个参数: // to:要去的路由信息(包含path、params等) // from:从哪个路由来的 // next:必须调用,否则路由不会继续跳转 // 假设你有一个简单的方法判断是否登录 const isLoggedIn = localStorage.getItem('token') // 有token就认为已登录 // 如果要去的是需要登录的页面(这里以meta.requiresAuth标记) if (to.meta.requiresAuth && !isLoggedIn) { // 没登录,跳到登录页 next('/login') } else { // 其他情况正常放行 next() // 不加参数就是正常跳转 } })在路由配置里标记哪些需要登录
javascript
const routes = [ { path: '/admin', component: () => import('@/views/AdminLayout.vue'), // meta是路由的元信息,随便放自定义数据 meta: { requiresAuth: true }, // 标记这个路由需要登录 children: [ // ...子路由 ] }, { path: '/login', name: 'login', component: () => import('@/views/LoginView.vue') } ]九、常见小问题,我踩过的坑
1. 路由变了但组件没重新渲染
有时候从/user/1跳到/user/2,组件是同一个,Vue会复用组件,created钩子不会再次触发。如果你依赖created去请求数据,数据就还是旧的。
解决办法:用 watch 监听路由变化:
javascript
import { watch } from 'vue' import { useRoute } from 'vue-router' const route = useRoute() // 监听路由参数变化,变化了就重新请求数据 watch( () => route.params.id, // 监听params里的id (newId) => { console.log('ID变了,新ID:', newId) fetchUserData(newId) // 重新获取数据 } )2. 加了history模式,刷新页面404
用createWebHistory()时,地址栏是干净的(没有#号)。但如果你直接访问/about然后刷新页面,浏览器会真的向服务器请求/about这个路径,而服务器上根本没有这个文件,就404了。
解决办法:开发环境下Vite已经帮你处理好了。上线时需要在Nginx里加配置,把所有的请求都指向index.html。
3. router-link点击后没有样式
router-link默认渲染成a标签,当它匹配到当前路由时,Vue Router会自动给它加上两个class:router-link-active和router-link-exact-active。你可以在CSS里给这两个类加样式,实现高亮当前菜单的效果。
css
/* 精确匹配时(比如 / 对 /,/about 对 /about) */ .router-link-exact-active { color: #1890ff; font-weight: bold; } /* 模糊匹配时(比如 /admin/users 和 /admin 也算匹配) */ .router-link-active { background: #f0f0f0; }十、总结一下你学到的
| 概念 | 干什么用的 | 关键代码 |
|---|---|---|
| 路由表 | 定义路径和组件的对应关系 | routes: [{path:'/xxx', component: Xxx}] |
| router-view | 指定组件渲染的位置 | <router-view /> |
| router-link | 生成导航链接 | <router-link to="/xxx"> |
| 动态路由 | 同一个组件,参数不同 | path: '/user/:id',取值$route.params.id |
| 编程式导航 | 用JS代码跳转 | router.push('/xxx') |
| 嵌套路由 | 页面里还有子页面 | 配置里写children,组件里放<router-view /> |
| 路由守卫 | 跳转前做检查(如登录) | router.beforeEach((to, from, next)=>{}) |
写在最后
Vue Router刚开始学,最容易迷惑的就是哪个组件在哪渲染。记住一个原则:
<router-view />在哪,组件就渲染在哪。
App.vue里那个是顶级出口,用来渲染所有一级路由。嵌套路由就是在父组件里再加一个<router-view />,渲染子路由。
多敲几个路由配置,切换来切换去,慢慢就习惯了。有问题评论区直接问,看到就回你。