在传统的 Web 开发过程中,当需要实现多个站内页面时,以前需要写很多个 HTML 页面,然后通过 标签来实现互相跳转。
在如今工程化模式下的前端开发,像 Vue 工程,可以轻松实现只用一个 HTML 文件,却能够完成多个站内页面渲染、跳转的功能,这就是路由。
TIP
从这里开始,所有包含到 .vue 文件引入的地方,
可能会看到 @xx/xx.vue 这样的写法。
@views 是 src/views 的路径别名,
@cp 是 src/components 的路径别名。
路径别名可以在 vite.config.ts 等构建工具配置文件里添加 alias ,
点击了解:添加项目配置
路由的目录结构
Vue 3 引入路由的方式和 Vue 2 一样,路由的管理也是放在 src/router 这个目录下:
src
│ # 路由目录
├─router
│ # 路由入口文件
├───index.ts
│ # 路由配置,如果路由很多,可以再拆分模块文件
├───routes.ts
│ # 项目入口文件
└─main.ts
其中 index.ts 是路由的入口文件,如果路由很少,那么可以只维护在这个文件里,但对复杂项目来说,往往需要配置上二级、三级路由,逻辑和配置都放到一个文件的话,太臃肿了。
所以如果项目稍微复杂一些,可以像上面这个结构一样拆分成两个文件: index.ts 和 routes.ts ,在 routes.ts 里维护路由树的结构,在 index.ts 导入路由树结构并激活路由,同时可以在该文件里配置路由钩子。
如果项目更加复杂,例如做一个 Admin 后台,可以按照业务模块,再把 routes 拆分得更细,例如 game.ts / member.ts / order.ts 等业务模块,再统一导入到 index.ts 文件里。
TIP
需要注意的是,与 Vue 3 配套的路由版本是 vue-router 4.x 以上才可以正确适配项目。
在项目里引入路由
不管是 Vue 2 还是 Vue 3 ,引入路由都是在 src/router/index.ts 文件里,但是版本升级带来的变化很大,由于本书关于 Vue 3 都是使用 TypeScript ,所以这里只做一个 TypeScript 的变化对比。
TIP
下文可能会出现多次 import.meta.env.BASE_URL 这个变量,
它是由 Vite 提供的环境变量,详见 Vite 官网关于 环境变量 的说明。
使用其他构建工具请自行替换为对应构建工具提供的环境变量,
例如使用 @vue/cli 创建的项目因为是基于 Webpack ,所以使用的是 process.env.BASE_URL 。
回顾 Vue 2
Vue 2 的引入方式如下(其中 RouteConfig 是路由项目的 TS 类型)。
import Vue from 'vue'
import VueRouter from 'vue-router'
import type {
RouteConfig } from 'vue-router'
Vue.use(VueRouter)
const routes: Array<RouteConfig> = [
// ...
]
const router = new VueRouter({
mode: 'history',
base: import.meta.env.BASE_URL,
routes,
})
export default router
里面一些选项的功能说明:
routes 是路由树的配置,当的路由很多的时候可以集中到 routes.ts 管理,然后再 import 进来(具体的配置请看后面的 路由配置部分 说明)。
mode 决定访问路径模式,可配置为 hash 或者 history , Hash 模式是这种 http://abc.com/#/home 这样带 # 号的地址,支持所有浏览器, History 模式是 http://abc.com/home 这样不带 # 号,不仅美观,而且体验更好,但需要服务端做一些配置支持(详见下文的 服务端配置方案 ),也只对主流浏览器支持。
base 是 History 模式在进行路由切换时的基础路径,默认是 / 根目录,如果的项目不是部署在根目录下,而是二级目录、三级目录等多级目录,就必须指定这个 base ,否则路由切换会有问题。
了解 Vue 3
Vue 3 的引入方式如下(其中 RouteRecordRaw 是路由项目的 TS 类型)。
import {
createRouter, createWebHistory } from 'vue-router'
import type {
RouteRecordRaw } from 'vue-router'
const routes: Array<RouteRecordRaw> = [
// ...
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
})
export default router
在 Vue 3 (也就是 vue-router 4.x) 里,路由简化了一些配置项,其中 routes 和 Vue 2 一样,是路由树的配置。
但是 history 和 Vue 2 有所不同,在 Vue 3 ,使用 history 来代替 Vue 2 的 mode ,但功能是一样的,也是决定访问路径模式是 Hash 模式 还是 History 模式,同时合并了 Vue 2 (也就是 vue-router 3.x) 的 base 选项作为模式函数的入参。
和在使用 Vue 2 的时候一样, Vue 3 也可以配置一些额外的路由选项,比如:指定 router-link 为当前激活的路由所匹配的 className :
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
linkActiveClass: 'cur',
linkExactActiveClass: 'cur',
routes,
})
更多的配置项可以参考 Vue Router 官网的 API 参考 一章。
路由树的配置
在 引入路由 部分有说到,当项目的路由很多的时候,文章会变得非常长,难以维护,这个时候可以集中到 routes.ts 或者更多的模块化文件管理,然后再 import 到 index.ts 里。
暂且把 routes.ts 这个文件称为 “路由树” ,因为它像一棵大树一样,不仅可以以一级路由为树干去生长,还可以添加二级、三级等多级路由来开枝散叶,下面来看看 routes.ts 应该怎么写。
基础格式
在 TypeScript 里,路由文件的基础格式由三个部分组成:类型声明、数组结构、模块导出。
// src/router/routes.ts
// 使用 TypeScript 时需要导入路由项目的类型声明
import type {
RouteRecordRaw } from 'vue-router'
// 使用路由项目类型声明一个路由数组
const routes: Array<RouteRecordRaw> = [
// ...
]
// 将路由数组导出给其他模块使用
export default routes
之后就可以在 index.ts 里导入使用了。
那么里面的路由数组又是怎么写呢?这里就涉及到了 一级路由 和 多级路由 的编写。
公共基础路径
在配置路由之前,需要先了解 “公共基础路径” 的概念,在讲解使用 Vite 等工具创建项目时,都提到了一个 项目配置 的管理,以 Vite 项目的配置文件 vite.config.ts 为例,里面有一个选项 base ,其实就是用来控制路由的公共基础路径,那么它有什么用呢?
base 的默认值是 /,也就是说,如果不配置它,那么所有的资源文件都是从域名根目录读取,如果项目部署在域名根目录那当然好,但是如果不是呢?那么就必须来配置它了。
配置很简单,只要把项目要上线的最终地址,去掉域名,剩下的那部分就是 base 的值。假设项目是部署在 https://example.com/vue3/ ,那么 base 就可以设置为 /vue3/。
TIP
一级路由的 path 都必须是以 / 开头,比如: /home、/setting;
如果的项目首页不想带上 home 之类的尾巴,只想要 https://example.com/ 这样的域名直达 ,其实也是配置一级路由,只需要把路由的 path 指定为 / 即可。
name 是路由的名称,非必填,但是一般都会配置上去,这样可以很方便的通过 name 来代替 path 实现路由的跳转,因为像有时候的开发环境和生产环境的路径不一致,或者说路径变更,通过 name 无需调整,但如果通过 path,可能就要修改很多文件里面的链接跳转目标了。
component 是路由的模板文件,指向一个 vue 组件,用于指定路由在浏览器端的视图渲染,这里有两种方式来指定使用哪个组件:
同步组件
字段 component 接收一个变量,变量的值就是对应的模板组件。
在打包的时候,会把组件的所有代码都打包到一个文件里,对于大项目来说,这种方式的首屏加载是个灾难,要面对文件过大带来等待时间变长的问题。
import Home from '@views/home.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: Home,
},
]
所以现在都推荐使用第二种方式,可以实现 路由懒加载 。
异步组件
字段 component 接收一个函数,在 return 的时候返回模板组件,同时组件里的代码在打包的时候都会生成独立的文件,并在访问到对应路由的时候按需引入。
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: () => import('@views/home.vue'),
},
]
关于这部分的更多说明,可以查看 路由懒加载。
多级路由
在 Vue 路由生态里,支持配置二级、三级、四级等多级路由,理论上没有上限,实际业务中用到的级数通常是三级到四级。
比如做一个美食类网站,打算在 “中餐” 大分类下配置一个 “饺子” 栏目,那么地址就是:
https://example.com/chinese-food/dumplings
这种情况下,中餐 chinese-food 就是一级路由,饺子 dumplings 就是二级路由。
如果想再细化一下,“饺子” 下面再增加一个 “韭菜” 、“白菜” 等不同馅料的子分类:
https://example.com/chinese-food/dumplings/chives
这里的韭菜 chives 就是饺子 dumplings 的子路由,也就是三级路由。
在了解了子路由的概念后,来看一下具体如何配置以及注意事项。
父子路由的关系,都是严格按照 JSON 的层级关系,子路由的信息配置到父级的 children 数组里面,孙路由也是按照一样的格式,配置到子路由的 children 里。
这是一个简单的子路由示范:
const routes: Array<RouteRecordRaw> = [
// 注意:这里是一级路由
{
path: '/lv1',
name: 'lv1',
component: () => import('@views/lv1.vue'),
// 注意:这里是二级路由,在 `path` 的前面没有 `/`
children: [
{
path: 'lv2',
name: 'lv2',
component: () => import('@views/lv2.vue'),
// 注意:这里是三级路由,在 `path` 的前面没有 `/`
children: [
{
path: 'lv3',
name: 'lv3',
component: () => import('@views/lv3.vue'),
},
],
},
],
},
]
上面这个配置,最终三级路由的访问地址如下:
https://example.com/lv1/lv2/lv3
可以看到在注释里提示了二级、三级路由的 path 字段前面没有 / ,这样路径前面才会有其父级路由的 path 以体现其层级关系,否则会从根目录开始。
路由懒加载
在上面提过,路由在配置 同步组件 的时候,构建出来的文件都集中在一起,大的项目的文件会变得非常大,影响页面加载。
所以 Vue 在 Webpack 的代码分割功能的基础上,推出了 异步组件,可以把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样按需载入,很方便的实现路由组件的懒加载。
在这一段配置里面:
const routes: Array<RouteRecordRaw>