1.怎么快速搭建Vue Router 3.x的基础路由?
在Vue 2项目开发里,Vue Router 3.x是实现单页面应用路由管理的核心工具,不管是做后台管理系统的页面跳转,还是移动端App的多页面切换,它都能帮我们把页面逻辑梳理得明明白白,但实际开发时,不少同学会卡在路由配置、传参、模式选择这些环节,今天就用问答形式,把Vue Router 3.x开发中常见的问题和解决思路聊透,帮你少踩坑~
要在Vue 2项目里用Vue Router 3.x,步骤其实很清晰:首先得安装依赖,打开终端执行 npm install vue-router@3
(指定3.x版本,避免装到适配Vue 3的4.x版本)。
然后创建路由配置文件,比如在src
下新建router/index.js
,基本结构长这样:
import Vue from 'vue' import VueRouter from 'vue-router' // 导入页面组件,这里用常规导入,后面讲懒加载时再换写法 import Home from '@/views/Home.vue' import About from '@/views/About.vue' Vue.use(VueRouter) // 全局注册VueRouter插件 const routes = [ { path: '/', // 访问根路径时 name: 'Home', component: Home // 渲染Home组件 }, { path: '/about', name: 'About', component: About } ] const router = new VueRouter({ routes // 把定义好的路由规则传进去 }) export default router
接着要把路由实例挂载到Vue根实例上,打开src/main.js
:
import Vue from 'vue' import App from './App.vue' import router from './router' // 导入上面写的路由配置 new Vue({ router, // 注入路由,让所有组件能通过this.$router/$route访问 render: h => h(App) }).$mount('#app')
最后在App.vue
里加入路由出口(<router-view></router-view>
)和导航(<router-link>
):
<template> <div id="app"> <!-- 导航链接,to属性对应路由path --> <router-link to="/">首页</router-link> <router-link to="/about">lt;/router-link> <!-- 路由匹配到的组件会渲染到这里 --> <router-view></router-view> </div> </template>
这样一套操作下来,就能实现基础的页面跳转了,重点是记住“安装插件→定义路由规则→挂载路由→用router-view和router-link渲染”这几个步骤~
hash模式和history模式有啥区别?该怎么选?
Vue Router 3.x支持两种路由模式,配置是通过new VueRouter
时的mode
选项:
-
hash模式(默认):URL里会带个,比如
http://xxx.com/#/home
,它的原理是利用URL的hash(锚点)变化不会触发页面刷新,浏览器会监听hashchange
事件,进而更新路由,这种模式对老浏览器(比如IE9)兼容性更好,而且部署时不需要后端额外配置,因为服务器只需要返回首页HTML,路由由前端自己处理。 -
history模式:URL更“干净”,像
http://xxx.com/home
这样,它依赖HTML5的history.pushState
API来实现URL变化但不刷新页面,优点是URL美观,和普通网站的URL结构一致;但缺点是部署时需要后端配合——如果用户直接访问某个子路由(比如http://xxx.com/about
),服务器没有对应的资源,就会返回404,这时候需要后端配置“所有请求都重定向到index.html”,让前端路由接管。
选的时候看项目需求:如果是对内系统、对兼容性要求高(比如要兼容IE),用hash模式省心;如果是对外的官网、需要URL更友好,且能配合后端做路由重定向,就用history模式,切换模式也很简单,改一下路由配置:
const router = new VueRouter({ mode: 'history', // 换成history,默认是hash routes })
路由传参有哪些方式?各自适合啥场景?
Vue Router 3.x里传参主要有动态路由(params)、查询参数(query)、props传参这三种方式,用法和场景各有不同:
① 动态路由(params)
适合需要把参数“嵌入”URL的场景,比如用户详情页/user/1
,其中1是用户ID,配置时要在路由规则里定义动态段:
{ path: '/user/:id', // :id是动态参数 name: 'User', component: User }
跳转时可以用router-link
:
<router-link :to="{ name: 'User', params: { id: 1 }}">用户1</router-link>
或者编程式导航:
this.$router.push({ name: 'User', params: { id: 1 }})
组件内获取参数用this.$route.params.id
,这种方式的好处是参数在URL里可见,刷新页面参数也不会丢;但如果参数多了,URL会很长,而且参数是必填的(比如访问/user
会匹配不到路由,除非配置了默认值)。
② 查询参数(query)
类似URL里的查询字符串,比如/user?name=张三&age=18
,参数跟在后面,配置路由时不需要改path,直接在跳转时传query对象:
<router-link :to="{ path: '/user', query: { name: '张三', age: 18 }}">用户页</router-link>
编程式导航:
this.$router.push({ path: '/user', query: { name: '张三', age: 18 }})
组件内用this.$route.query.name
获取,这种方式适合可选参数,比如搜索页的筛选条件,不传参数也能正常访问页面,而且参数可以多个,灵活性高;但缺点是参数暴露在URL里,不太适合敏感信息。
③ props传参
能让路由组件和参数“解耦”,更符合组件化思想,开启props有三种方式:
-
布尔模式:路由配置里设
props: true
,此时组件可以通过props接收$route.params
的参数:{ path: '/user/:id', component: User, props: true // 开启后,User组件可以用props: ['id']接收 }
-
对象模式:适合传固定值,不管URL怎么变,props值不变:
{ path: '/page', component: Page, props: { title: '固定标题' } // Page组件用props: ['title']接收 }
-
函数模式:最灵活,可以对参数做处理,比如结合query和params:
{ path: '/article', component: Article, props: route => ({ id: route.query.id, type: route.params.type }) }
用props的好处是组件不需要依赖$route
,更易测试和复用;缺点是如果参数多,配置起来稍麻烦。
总结下来:需要URL体现参数、参数必填→用动态路由;可选参数、多参数→用query;想让组件更纯净→用props~
导航守卫怎么用?能解决哪些实际问题?
导航守卫就是路由切换过程中的“钩子函数”,能在路由跳转前、后做拦截或处理,常见的有全局守卫、路由独享守卫、组件内守卫三类,每个场景都有对应的用法:
① 全局守卫(控制全局权限)
比如全局判断用户是否登录,没登录就跳登录页,用router.beforeEach
,在路由配置文件里写:
router.beforeEach((to, from, next) => { // to: 要跳转到的目标路由;from: 从哪个路由来;next: 必须调用的函数,决定是否跳转 const isLogin = localStorage.getItem('token') // 假设用localStorage存登录态 if (to.name !== 'Login' && !isLogin) { next({ name: 'Login' }) // 没登录且不是登录页,跳登录 } else { next() // 放行 } })
除了权限,还能做全局loading:跳转时显示loading,成功后隐藏,结合router.beforeEach
和router.afterEach
(afterEach没有next,只做回调)。
② 路由独享守卫(单个路由的拦截)
比如某个页面需要特殊权限,在路由规则里加beforeEnter
:
{ path: '/admin', name: 'Admin', component: Admin, beforeEnter: (to, from, next) => { const isAdmin = localStorage.getItem('isAdmin') if (isAdmin) { next() } else { next({ name: 'Home' }) // 不是管理员跳首页 } } }
③ 组件内守卫(组件生命周期内的路由处理)
常用的有beforeRouteEnter
(进入组件前,此时组件实例还没创建,不能用this)、beforeRouteUpdate
(路由复用组件时触发,比如从/user/1
到/user/2
,组件复用,需要更新数据)、beforeRouteLeave
(离开组件前,比如判断表单是否修改,提示用户)。
举个beforeRouteUpdate
的例子,用户页复用组件时更新数据:
export default { name: 'User', data() { return { userInfo: {} } }, beforeRouteUpdate(to, from, next) { // to.params.id是新的用户ID this.fetchUser(to.params.id) // 调用接口重新获取数据 next() }, methods: { fetchUser(id) { // 发请求获取用户信息 } } }
导航守卫的核心是控制路由流向和处理页面切换时的逻辑,比如权限、数据预加载、页面离开提示这些场景,都能靠它解决~
路由懒加载怎么配置?对性能有啥帮助?
路由懒加载本质是按需加载组件,让首屏不需要加载所有页面的代码,等用户访问对应路由时再加载,从而减小首屏体积、加快加载速度。
Vue Router 3.x里配置懒加载很简单,把原来的静态导入(import Home from '@/views/Home.vue'
)换成动态导入:
const Home = () => import('@/views/Home.vue') const About = () => import('@/views/About.vue') const routes = [ { path: '/', component: Home }, { path: '/about', component: About } ]
这里的import()
是ES6的动态导入语法,Webpack会自动把每个组件打包成单独的chunk(代码块),访问对应路由时才会加载这个chunk。
对性能的帮助很直观:假设项目有10个页面,首屏只加载首页和必要组件,其他9个页面的代码等到用户点击时再加载,首屏JS体积能减少一大半,加载速度自然更快,尤其是页面多、组件大的项目,懒加载能明显提升用户体验。
如果想给 chunk 命名(方便调试),可以用Webpack的魔法注释:
const Home = () => import(/* webpackChunkName: "home" */ '@/views/Home.vue')
嵌套路由怎么设计更合理?
嵌套路由就是“父路由里包含子路由”,典型场景是后台管理系统:比如有个Layout
组件(包含侧边栏、顶栏),里面嵌套Dashboard
、Settings
等子页面。
配置时用children
数组,父路由的组件里必须有<router-view></router-view>
来显示子路由:
第一步,定义路由规则(router/index.js
):
const Layout = () => import('@/views/Layout.vue') const Dashboard = () => import('@/views/Dashboard.vue') const Settings = () => import('@/views/Settings.vue') const routes = [ { path: '/admin', component: Layout, // 父组件,包含侧边栏等公共布局 children: [ { path: 'dashboard', // 注意!这里path不加/,会自动拼接成/admin/dashboard name: 'Dashboard', component: Dashboard }, { path: 'settings', name: 'Settings', component: Settings } ] } ]
第二步,在父组件Layout.vue
里加入子路由出口:
<template> <div class="layout"> <!-- 侧边栏、顶栏等公共部分 --> <aside>侧边栏</aside> <header>顶栏</header> <!-- 子路由组件渲染到这里 --> <router-view></router-view> </div> </template>
设计嵌套路由的关键是明确页面层级:父路由负责公共布局,子路由负责具体页面内容,这样做能让代码结构更清晰,避免重复写布局组件~
动态路由匹配和路由参数怎么处理?
动态路由匹配指的是路由规则里有动态段(比如/user/:id
),当从/user/1
跳转到/user/2
时,Vue Router会复用组件实例(因为组件一样),这时候组件的created
钩子不会重新执行,导致数据没更新。
这时候有两种解决办法:
① 监听$route变化
在组件里用watch
监听$route
,路由变化时更新数据:
export default { name: 'User', data() { return { userInfo: {} } }, watch: { '$route'(to) { this.fetchUser(to.params.id) // 路由变化时重新请求数据 } }, methods: { fetchUser(id) { /* 发请求 */ } } }
② 使用组件内守卫beforeRouteUpdate
这种方式更“针对性”,只处理路由更新的情况:
export default { name: 'User', data() { return { userInfo: {} } }, beforeRouteUpdate(to, from, next) { this.fetchUser(to.params.id) next() // 必须调用next放行 }, methods: { fetchUser(id) { /* 发请求 */ } } }
如果需要在路由跳转前就获取数据(比如进入页面时数据已经准备好),可以用导航守卫配合异步请求,比如在beforeRouteEnter
里发请求,数据回来后再进入页面~
路由出错(比如404页面)怎么处理?
项目里难免遇到用户输错地址、访问不存在的路由的情况,这时候要跳转到404页面,配置思路是*用通配符路由()匹配所有未定义的路径,但要注意路由的匹配顺序——通配符路由必须放在routes数组的最后面**,因为Vue Router是从上到下匹配路由的。
步骤如下:
第一步,创建NotFound.vue
组件,显示“页面不存在”之类的提示。
第二步,在路由规则里添加通配符路由:
const NotFound = () => import('@/views/NotFound.vue') const routes = [ // 其他路由规则... { path: '*', // 匹配所有未定义的路径 name: 'NotFound', component: NotFound } ]
这样,当用户访问不存在的路径时,就会渲染NotFound组件,如果项目用了history模式,还要确保后端配置了“所有未知请求都返回index.html”,否则直接访问/abc
这种路径,服务器会返回404,而不是前端的404页面~
Vue Router 3.x和Vue 2.x版本适配要注意啥?
Vue Router的版本和Vue版本是强绑定的:Vue 2对应Vue Router 3.x,Vue 3对应Vue Router 4.x,如果搞错版本,会出现各种兼容问题,
- 安装时如果直接执行
npm install vue-router
,默认会装最新的4.x版本,而Vue 2项目用不了,会报类似“export 'createRouter' was not found in 'vue-router'”的错误(因为Vue Router 4用createRouter
创建实例,而3.x是new Router
)。
所以正确的做法是指定版本安装:
npm install vue-router@3
Vue Router 3.x的API和4.x有很大区别,
- x用
new VueRouter({ routes })
创建实例,4.x用createRouter
; - x的模式是
mode: 'history'
,4.x是history: createWebHistory()
; - 导航守卫的语法也有变化(比如3.x的
beforeRouteEnter
在4.x里写法不同)。
如果项目是Vue 2,一定要用Vue Router 3.x;如果
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。