04-Vue2路由

image-20200723170734421

参考资料:

1. 路由

根据不同的路径切换不同的页面。

1.1 SPA 单页面应用

SPA,单页面应用(SinglePage Web Application)。

MPA,多页面应用(MultiPageApplication)。

单页面应用(SPA) 多页面应用(MPA)
组成 一个外壳和多个页面片段组成 多个完整页面构成
资源共用(css, js) 共用,只需在外壳部分加载 不共用,每个页面都需要加载
刷新方式 页面局部刷新或更改 整页刷新
url模式 a.com/#/pageone
a.com/#/pagetwo
a.com/pageone.html
a.com/pagetwo.html
用户体验 页面片段间的切换快,用户体验良好 页面切换加载缓慢,流畅度不够,用户体验比较差
转场动画 容易实现 无法实现
数据传递 容易 依赖url传参、或者cookie、localStorage等
搜索引擎优化(SEO) 需要单独方案、实现较为困难、不利于SEO检索、可利用服务器端渲染(SSR)优化 实现方法简易
试用范围 高要求的体验度、追求界面流畅的应用 试用于追求高度支持搜索引擎的应用
开发成本 较高,常需借助专业的框架 较低,但页面重复代码多
维护成本 相对容易 相对复杂

单页面应用与多页面应用的对比:https://www.cnblogs.com/zxlh1529/p/18796998

1.2 vue-router

路由可以建立 路径与组件 的映射关系。

vue-cli 创建的时候勾选 或 单独安装,同时确保 main.js 中 router 是开启的,可以在 package.json 中检查确认。

package.json

1
2
3
4
5
"dependencies": {
...
"vue-router": "^3.5.1",
...
},

main.js

1
2
3
4
5
6
7
8
9
10
11
//...
import router from './router'
//...

Vue.config.productionTip = false

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

1.3 路由配置

router/index.js 内的 routes 路由表配置,配置前需要在当前文件顶部进行 import 导入。

<router-view> 可以通过路由容器类似插槽)将组件加载进来,对应访问路径配置的组件。

如目录 views 下创建有多个组件:

1
2
3
4
5
6
7
8
.
├── src/
│   ├── router/
│   │   └── index.js
│   ├── views/
│   │   ├── Center.vue
│   │   ├── Cinemas.vue
│   │   └── Films.vue

Center.vue | Cinemas.vue | Films.vue

1
2
3
4
5
<template>
<div>
center | cinemas | films
</div>
</template>

router/index.js

  • import 导入新增的路由组件
  • routes 配置表添加对应的路径 path 和组件 component,建立路径与组件的关联
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入新增的路由组件
import Films from '@/views/Films.vue'
import Center from '@/views/Center.vue'
import Cinemas from '@/views/Cinemas.vue'

Vue.use(VueRouter) // 注册路由插件,两个全局组件:router-view, router-link

// 路由配置表添加
const routes = [
{
path: '/films',
component: Films
},
{
path: '/center',
component: Center
},
{
path: '/cinemas',
component: Cinemas
},
]

const router = new VueRouter({
routes
})

export default router

App.vue

1
2
3
4
5
6
7
8
/* dom */
<template>
<div>
hello app-{{ myname }}
<!-- 路由容器(类似插槽) -->
<router-view></router-view>
</div>
</template>

效果:

访问不同的路径得到不同的组件加载渲染的内容:

1
2
3
http://localhost:8080/#/cinemas
hello app-jerry
cinemas
1
2
3
http://localhost:8080/#/center
hello app-jerry
center
1
2
3
http://localhost:8080/#/films
hello app-jerry
films

1.4 重定向

routes 中配置 path 为根目录,对应 redirect 到具体的目录。

  • 配置无顺序要求。
  • path: '*', 时可以防止随意访问路径导致页面没有组件可加载,即无内容显示的问题
1
2
3
4
5
6
7
8
// 路由配置表
const routes = [
// 重定向:访问任何不存在的目录时,自动重定向到 /films
{
path: '*', // 根目录 path: '/'
redirect: '/films'
}
]

1.5.1 常规使用

编程式导航:js跳转,即location.href="url" 跳转到具体的url。

声明式导航:之前使用 a 链接跳转,vue中使用 <router-link> 实现跳转和高亮。

  • 参数

    • to,目标页面
    • active-class,该属性默认不需要添加,如果添加则是指定当前激活的a标签的类,用于高亮
  • 原理

    • hash 路由:
      • location.hash 切换,window.onhashchange 监听路径的切换
    • history 路由:
      • history.pushState 切换,window.onpopstate 监听路径的切换

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>
hello app-{{ myname }}
<ul>
<li>
<a href="/#/films">电影</a>
</li>
<li>
<a href="/#/cinemas">影院</a>
</li>
<li>
<a href="/#/center">我的</a>
</li>
</ul>

<!-- 路由容器(类似插槽) -->
<router-view></router-view>
</div>
</template>
...

使用声明式导航 router-link:

1
2
3
4
5
<ul>
<li>
<router-link to="/films">电影</router-link>
</li>
</ul>
1
2
<!-- 也可以对 active 类进行重命名 -->
<router-link to="/films" active-class="jerry-active">电影</router-link>

实际HTML在点击时会自动转为 a 标签并添加加 class:

1
<a href="#/films" class="router-link-exact-active router-link-active" aria-current="page">电影</a>

App.vue 就可以对其进行高亮显示设置:

1
2
3
4
5
<style lang="scss" scoped>
.router-link-active{
color: red;
}
</style>

1.5.2 版本差异

vue-router 3 和之前,比如 不需要li标签包裹,但是可以指定 tag 让其渲染成为 li 标签包裹

1
2
3
4
<ul>
<router-link to="/films" tag="li">电影</router-link>
<router-link to="/cinemas">影院</router-link>
</ul>

vue-router 4 后支持,更强大的扩展能力,使用 custom + v-slot 来调用navigate函数确定是否isActive选中,参考

1
2
3
4
5
6
7
8
9
10
11
12
<ul>
<router-link to="/films" custom v-slot="{navigate, isActive}">
<li @click="navigate" :class="isActive ? 'router-link-active' : ''">
电影-{{isActive}}
</li>
</router-link>
<router-link to="/cinemas" custom v-slot="{navigate, isActive}">
<li @click="navigate" :class="isActive ? 'router-link-active' : ''">
影院-{{isActive}}
</li>
</router-link>
</ul>

1.6 嵌套路由

1.6.1 真·二级路径

目录结构为真实父子关系的组件,路径也是父子关系,子组件对应二级路径

1
2
3
4
5
6
7
8
9
10
.
├── src/
│   ├── router/
│   │   └── index.js
│   ├── views/
│   │   ├── films/
│   │   │   ├── Comingsoon.vue
│   │   │   └── Nowplaying.vue
│   │   ├── ...
│   │   └── Films.vue

views/Films.vue (<router-view> 父组件标签用于二级声明式导航组件渲染,渲染内容对应路由表中 children)

1
2
3
4
5
6
7
8
<template>
<div>
<div style="height: 200px; background: yellow">大轮播</div>
<div>二级声明式导航</div>
<!-- router-view 二级声明式导航组件对象渲染位置 -->
<router-view></router-view>
</div>
</template>

views/films/Comingsoon.vue | views/films/Nowplaying.vue

1
2
3
4
5
<template>
<div>
Coming soon... | Now playing...
</div>
</template>

router/index.js (包含二级路径在父路径访问时的默认重定向)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
...
// 导入新增的路由组件
import Films from '@/views/Films.vue'
import Comingsoon from '@/views/films/Comingsoon.vue'
import Nowplaying from '@/views/films/Nowplaying.vue'
...

// 路由配置表
const routes = [
{
path: '/films',
component: Films,
// 嵌套路由(该路径是二级路径)
children: [
{
path: '/films/nowplaying',
component: Nowplaying
},
{
path: '/films/comingsoon',
component: Comingsoon
},
// 重定向:只要进入films就默认到 /nowplaying 页面
{
path: '/films',
redirect: '/films/nowplaying'
}
]
},
...
]
...

效果:

image-20251223161555257

1.6.2 假·二级路径

目录结构为同级目录结构,没有子级,只是访问路径上看起来是二级路径

1
2
3
4
5
6
7
.
├── src/
│   ├── router/
│   │   └── index.js
│   ├── views/
│   │   ├── Search.vue
│   │   └── Cinemas.vue

views/Cinemas.vue | views/Search.vue

1
2
3
4
5
<template>
<div>
cinemas... | search...
</div>
</template>

router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
import Cinemas from '@/views/Cinemas.vue'
import Search from '@/views/Search.vue'
...

// 路由配置表
const routes = [
...
{
path: '/cinemas',
component: Cinemas
},
// 嵌套路由(但不是二级路径,实际是替换了组件)
{
path: '/cinemas/search',
component: Search
},
...
]
...

效果:

image-20251223161739814

1.7 $router 编程式导航

this.$router.push(path) 编程式导航,path 使用的是路由配置的相对路经。

  • this.$router 等价于 main.js 中初始化的 router 对象。

示例:

views/films/Nowplaying.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div>
Now playing...
<ul>
<li v-for='item in datalist' :key='item' @click='handleChangePage'>
{{item}}
</li>
</ul>
</div>
</template>

<script>
export default {
data() {
return {
datalist: ['111', '222', '333']
}
},
methods: {
handleChangePage() {
// 编程式导航 - dom写法
// location.href = '/#/detail'
// 编程式导航 - vue写法
this.$router.push('/detail') //未传参数,仅跳转
}
}
}
</script>

router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
...
import Detail from '@/views/Detail.vue'
import Nowplaying from '@/views/films/Nowplaying.vue'
...

// 路由配置表
const routes = [
{
path: '/films',
component: Films,
children: [
{
path: '/films/nowplaying',
component: Nowplaying
},
{
path: '/films/comingsoon',
component: Comingsoon
},
// 重定向
{
path: '/films',
redirect: '/films/nowplaying'
}
]
},
{
path: '/detail',
component: Detail
},
...

1.8 动态路由

典型应用:列表跳转详情。

1.路由path: '/detail/:id', 动态二级路由,id可以自定义命名,匹配的路径必须拼接了id的值才能访问

2.传参:注意是 $router,.push()方法等价于 location.href 的页面路径跳转。

1
this.$router.push(`/detail/${id}`)

3.接参:注意是 $route,参数在对象 params 中。

1
this.$route.params.id

router/index.js

1
2
3
4
5
6
7
8
// 路由配置表
const routes = [
{
path: '/detail/:id', // 动态二级路由,只能是二级,id是匹配必须拼接id才能访问
component: Detail
},
...
]

编程式导航-列表跳转详情传参:views/films/Nowplaying.vue

1
2
3
4
5
6
7
8
9
10
11
12
...
<script>
export default {
...
methods: {
handleChangePage(id) {
// 1. vue编程式导航写法 + 字符串模版, id通过绑定的点击事件函数参数传进来
this.$router.push(`/detail/${id}`)
}
}
}
</script>

详情页接收参数:views/Detail.vue

1
2
3
4
5
6
7
8
9
<script>
export default {
created() {
// 当前匹配的路由 - 详情页
console.log("详情页", this.$route.params.id)
// axios 利用id发请求到详情接口,获取详情数据,布局页面
}
}
</script>

1.9 命名路由

路由配置表中给路由指定 name 属性,可以通过 name 进行跳转或重定向。

router/index.js

1
2
3
4
5
6
7
8
9
// 路由配置表
const routes = [
{
name: 'filmDetail', // 命名路由
path: '/detail/:id',
component: Detail
},
...
]

通过命名路由跳转:views/films/Nowplaying.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
<script>
export default {
...
methods: {
handleChangePage(id) {
// 2.通过命名路由跳转 + 传参id
this.$router.push({
name: 'filmDetail',
params: {
id // id名字一样,简写
}
})
}
}
}
</script>

1.10 路由模式 ×2

hash 哈希路由模式,路径会带# 号分隔。

histroy History路由模式,路径不带# 号。参考官方说明和解决方案

router/index.js

1
2
3
4
5
6
7
...
const router = new VueRouter({
// mode: 'hash', // hash模式,路径中带 #
mode: 'history', // history模式,路径干净不带 #
routes
})
...

理由有二:

  1. 路径好看,专业
  2. 分享url时,路径不会被截断。

风险问题(官方说明):

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。

所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

【总结】如果后端没有配置默认返回index.html,就可能会出现404的情况。

【解决】nginx 配置加这一句:

1
2
3
location / {
try_files $uri $uri/ /index.html;
}

1.11 路由拦截

场景:用户是否登陆的拦截判定。

1.11.1 全局路由拦截

meta 路由元信息,该字段在路由配置表中 routes 数组中可以自定义命名字段,标记拦截使用。

router.beforeEach((to, from, next) => {...} 全局拦截(路由守卫)配置逻辑。

  • to - 目标页面
  • from - 来源页面
  • next() - 放行,继续往下走;如果 next(path) 则进入指定页面,如 next('/login')

router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 路由配置表
const routes = [
...
{
path: '/center',
component: Center,
meta: {
isLoginRequired: true // 自定义字段,自行命名,标记拦截使用
}
},
{
path: '/login',
component: Login
},
...
]
...
// 全局拦截: to-目标页面, from-来源页面, next() 放行
router.beforeEach((to, from, next) => {
if (to.meta.isLoginRequired) { // 判断 meta 中定义的自定义字段 isLoginRequired
if (localStorage.getItem('token')) { // 判断本地存储中是否有 token
next()
} else { // 授权未通过,下一步进 login 页面去登录
next({
path: '/login',
query: { redirect: to.fullPath } // 记录到 query 字段中进入 login 路由前的路由-以便登陆后回来
})
}
} else {
next()
}
})
...

views/Login.vue - 模拟登陆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div>
Need Login.....
<button @click="handleLogin">登陆</button>
</div>
</template>

<script>
export default {
methods: {
handleLogin() {
// 模拟登陆
setTimeout(() => {
// 登陆完成后拿到token存起来
localStorage.setItem('token', '后端返回的token字段')
// this.$router.back() // 登陆完返回上一页

// 1.获取query字段
// console.log(this.$route.query.redirect)
// 2.跳转到源页面(跳转被拦截到登陆页前的页面)
this.$router.push(this.$route.query.redirect)
}, 500)
}
}
}
</script>

效果:

chrome-capture-2025-12-23

1.11.2 局部路由拦截

beforeEnter 路由独享守卫,即局部路由拦截。

  • 写在路由表内部
1
2
3
4
5
6
7
8
9
10
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]

1.11.3 组件内的拦截

组件内的守卫,也叫路由的生命周期

可以为路由组件添加以下配置:

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave

views/Center.vue - 我的页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
export default {
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
if (localStorage.getItem('token')) {
next()
} else {
next('/login')
}
},
}
</script>

1.12 路由懒加载

场景:当网站的模块和体量较大时,打包后js包会变很大,影响页面首屏加载。

方案:路由懒加载 - 作为项目变大后的优化手段

  • 把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件。
  • 基于底层的 webpack 代码分隔功能,npm run build 编译打包出来后,js文件就会分离,以实现访问才加载。
  • 项目足够小的时候,也没必要走懒加载

实现:compontent: () => import('@views/xxx.vue') 路由配置表中函数式写法。

router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Vue from 'vue'
...
// import Center from '@/views/Center.vue' // 手动导入的需要删除
...

// 路由配置表
const routes = [
...
{
path: '/center',
// component: Center,
component: () => import('@/views/Center.vue'), // 路由懒加载(函数式写法)
meta: {
isLoginRequired: true
}
},
...
]
...

image-20251223190307239


04-Vue2路由
https://janycode.github.io/2018/05/22/04_大前端/04_Vue/04-Vue2路由/
作者
Jerry(姜源)
发布于
2018年5月22日
许可协议