devStandard/docs/learning/5-vue/2-Vue Router 3.x.md
2025-03-29 14:35:49 +08:00

736 lines
16 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Vue Router 3.x
> Vue Router 是 [Vue.js](http://cn.vuejs.org/) 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:
>
> * 嵌套的路由/视图表
> * 模块化的、基于组件的路由配置
> * 路由参数、查询、通配符
> * 基于 Vue.js 过渡系统的视图过渡效果
> * 细粒度的导航控制
> * 带有自动激活的 CSS class 的链接
> * HTML5 历史模式或 hash 模式,在 IE9 中自动降级
> * 自定义的滚动条行为
Vue Router官网 https://v3.router.vuejs.org/
## 使用
### 安装
```sh
npm install vue-router
```
### 使用
1. 导入 `vue vue-router`
2. `Vue.use(Router)` 安装路由插件
3. 创建路由映射表 routes
4. `new Router()` 创建路由实例
5. `App.vue` 添加 `<router-view />` 根据url 渲染对应的组件
6.`new Vue()` 中注入路由实例
### 名词
* router 路由实例/路由器
* routes 路由映射表/路由配置
* route 当前路由
### example
`src/router/index.js`
```js
import Vue from 'vue'
import Router from 'vue-router'
import approvalsRouter from './modules/approvals'
import departmentsRouter from './modules/departments'
import employeesRouter from './modules/employees'
import permissionRouter from './modules/permission'
import attendancesRouter from './modules/attendances'
import salarysRouter from './modules/salarys'
import settingRouter from './modules/setting'
import socialRouter from './modules/social'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
// 创建静态路由对象: 登录, 首页, 404
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '首页', icon: 'dashboard' }
}]
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/import',
component: Layout,
hidden: true, // 隐藏在左侧菜单中
children: [{
path: '', // 二级路由path什么都不写 表示二级默认路由
component: () => import('@/views/import')
}]
},
// 404 通配路由应该放到最后, 有顺序 !!!
{ path: '*', redirect: '/404', hidden: true }
]
// 动态路由
export const asyncRoutes = [
departmentsRouter,
employeesRouter,
settingRouter,
permissionRouter,
socialRouter,
attendancesRouter,
salarysRouter,
approvalsRouter
]
const createRouter = () => new Router({
// mode: 'history', // 需要服务器支持, 默认使用hash模式
scrollBehavior: () => ({ y: 0 }), // 切换路由时, 让页面回到顶部
routes: [...constantRoutes, ...asyncRoutes] // 合并静态路由和动态路由
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
```
`src/router/modules/approvals.js`
```js
// 导出属于审批的路由规则
import Layout from '@/layout'
// { path: '', component: '' }
// 每个子模块 其实 都是外层是layout 组件位于layout的二级路由里面
export default {
path: '/approvals', // 路径
name: 'approvals', // 给路由规则加一个name
component: Layout, // 组件
// 配置二级路的路由表
children: [{
path: '', // 这里当二级路由的path什么都不写的时候 表示该路由为当前二级路由的默认路由
component: () => import('@/views/approvals'),
// 路由元信息: 其实就是存储数据的对象 我们可以在这里放置一些信息
meta: {
title: '审批',
// meta属性的里面的属性随意定义 这里用title是因为左侧导航会读取路由里的meta里面的title作为显示菜单名称,
icon: 'tree-table'
}
}]
}
// 当你的访问地址是 /employees的时候,layout组件会显示,二级路由的默认组件也会显示
```
`src/App.vue`
```vue
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
```
`src/main.js`
```js
// ...
new Vue({
el: '#app',
router,
// ...
})
```
## 路由跳转
### \<router-link \/\>
* `<router-link />` 默认渲染成 a 标签
* `<router-view />` 渲染路由匹配的组件
```html
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
```
#### to 绑定对象
```js
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
```
对应
```js
router.push({ name: 'user', params: { userId: 123 } })
```
### 函数
#### router. push()
`<router-link />` 实际就是通过 `router.push()` 实现的, 会向 history 栈添加一个新的记录, 可以后退
```js
router.push(location, onComplete?, onAbort?)
```
example:
```js
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
// 提供了 pathparams 会被忽略。正确写法是使用完整的path, 或者使用name
// <router-link /> 的 to 参数也一样
router.push({ path: `/user/${userId}` })
router.push({ name: 'user', params: { userId }})
```
* query 会在地址栏显示, params 不显示
* 一般 query 用 path, params 用 name
#### router.replace()
`router. push()` 的区别就是不会往 history 栈添加新记录, 直接替换掉了
```js
router.replace(location, onComplete?, onAbort?)
```
模板中使用添加 `replace` 属性
```html
<router-link :to="..." replace>
```
#### router. go()
在 history 记录中向前或者后退多少步,类似 `window.history.go(n)`
```js
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
// 如果 history 记录不够用,静默失败
router.go(-100)
router.go(100)
```
## 动态路由
### 动态路径
```json
{ path: '/user/:id', component: User }
```
* 也可以有多个参数
```json
{ path: '/user/:user_id/post/post_id', component: Post }
```
可以通过 `$route.params` 拿到路径信息 ( `$route.query` 可以拿到查询信息)
| 模式 | 匹配路径 | $route.params |
| ----------------------------- | ------------------- | -------------------------------------- |
| /user/:username | /user/evan | `{ username: 'evan' }` |
| /user/:username/post/:post_id | /user/evan/post/123 | `{ username: 'evan', post_id: '123' }` |
### 组件的复用
**路由参数发生变化时, 组件并不会销毁并重新创建, 而是复用, 生命周期勾子只调用一次.**
解决:
* 方法一: watch `$route` 的变化
```js
watch: {
$route(to, from) {
// 对路由变化作出响应...
}
```
* 方法二: 使用导航守卫 `beforeRouteUpdate`
```js
beforeRouteUpdate(to, from, next) {
// react to route changes...
// don't forget to call next()
}
```
## 嵌套路由
> 组件嵌套, 路由也对应着嵌套
```
/user/xiaomin/profile /user/xiaomin/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
```
`App.vue` 里的 `<router-view />` 对应 url `/user/:id` 渲染 `User.vue` 页面, 在 `User.vue` 中也有一个 `<router-view />` 对应 url `/user/:id/profile` 渲染 `Profile.vue` 页面
子路由的配置放在 children 里
```js
const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
}
]
}
]
})
```
### 默认子路由
路径为空时, 在访问上级路径会默认渲染 children 组件
```js
children: [
// path为空, 在访问 /user/foo 时 UserHome 会被渲染在 User 的 <router-view> 中
{ path: '', component: UserHome }
]
```
## 命名视图
### 同级展示多个视图
* components 值是个对象
* 没有添加 name 的对应 default
```html
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
```
```json
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
```
### 在嵌套路由中同级展示多个视图
```
/settings/emails /settings/profile
+-----------------------------------+ +------------------------------+
| UserSettings | | UserSettings |
| +-----+-------------------------+ | | +-----+--------------------+ |
| | Nav | UserEmailsSubscriptions | | +------------> | | Nav | UserProfile | |
| | +-------------------------+ | | | +--------------------+ |
| | | | | | | | UserProfilePreview | |
| +-----+-------------------------+ | | +-----+--------------------+ |
+-----------------------------------+ +------------------------------+
```
nav 是普通组件, 在 UserSettings 视图下, 需要同时展示 UserProfile 和 UserProfilePreview 视图
```json
{
path: '/settings',
component: UserSettings,
children: [
{
path: 'emails',
component: UserEmailsSubscriptions
},
{
path: 'profile',
components: {
default: UserProfile,
helper: UserProfilePreview
}
}
]
}
```
## 重定向
`/a` 重定向到 `/b`
```json
// 路径
routes: [
{ path: '/a', redirect: '/b' }
]
// 重定向的目标也可以是一个命名的路由:
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
// 动态返回重定向目标
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
```
## 别名
访问 `/b` 就和访问 `/a` 一样
```js
routes: [
{ path: '/a', component: A, alias: '/b' }
]
```
## hash 模式和 history 模式
hash 模式 url 中带 `#`, 实际页面不会重新加载
history 需要后端配合配置, 设置 nginx 等相关软件, 把所有 url 都返回 index. html
```conf
// nginx
location / {
try_files $uri $uri/ /index.html;
}
```
1. 阻止默认跳转行为
2. 使用 history.pushState() 更新URL
3. 通过路由映射匹配组件
4. 仅更新\<router-view\>内容区域
## 路由组件传参
* 在路由映射表添加 `props`, 然后在组件的 `props` 里接收, 这样就不会导致于组件只能在某些 url 中使用
### 布尔模式
```json
routes: [
// props 被设置为 trueroute.params 将会被设置为组件属性
{ path: '/user/:id', component: User, props: true },
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 props 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
```
```vue
<!-- UserProfile.vue -->
<template>
<div>
<h1>用户ID: {{ id }}</h1>
</div>
</template>
<script>
export default {
props: {
id: {
type: String,
required: true
}
}
}
</script>
```
### 对象模式
* 这个对象的所有属性会被直接作为props传递给组件, 适用于 props 是静态的
```json
routes: [
{
path: '/promotion/from-newsletter',
component: Promotion,
props: { newsletterPopup: false }
}
]
```
```vue
<script>
export default {
props: {
newsletterPopup: {
type: Boolean,
default: true // 默认值会被路由配置覆盖
}
}
}
</script>
```
### 函数模式
结合静态值和路由, 返回需要的属性
```json
routes: [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
```
## 导航守卫
> 路由正在发生改变时, 进行 跳转或取消
### router.beforeEach() 全局前置守卫
```js
router.beforeEach((to, from, next) => {
console.log(to) //要进入的路由对象
console.log(from) //正离开的路由对象
console.log(next) //函数
next() //放行, 放最后一行, 不放行的话一直等待中, 不会刷出页面
//next()进行管道中的下一个钩子
//next('/') 或者next({path:'/'}) 跳转到一个不同的地址
//next(false) 中断当前的导航, 重置到 from 路由对应的地址
//next(error) 导航会被终止且该错误会被传递给 router.onError() 注册过的回调
})
```
### router.afterEach() 全局后置钩子
* 不会接受 next 函数也不会改变导航本身
```js
router.afterEach((to, from) => {
// ...
})
```
### 路由独享的守卫
```json
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
```
### 组件内的守卫
```vue
<script>
export default {
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
</script>
```
## 路由元信息
> 一个路由匹配到的所有路由记录会暴露为 `$route.matched` 数组
定义路由的时候可以配置 `meta` 字段
```js
{
name: 'useredit',
path: '/useredit',
component: () => import('@/views/UserEdit.vue'),
meta: {
isAuth: true
}
}
```
### 在组件中访问
`to.meta.isAuth` `from.meta.isAuth`
### 在全局导航守卫
```js
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// 检查认证状态
if (!store.state.isLoggedIn) next('/login')
else next()
} else {
next()
}
})
```
## 路由懒加载
> 当路由被访问的时候才加载对应组件
异步加载组件(懒加载):
```js
{
// 进行路由配置,规定'/'引入到home组件
path: '/',
component: resolve => require(['../pages/home.vue'], resolve),
meta: {
title: 'home'
}
}]
```
更多的写法
```js
{
path: '/',
component: () => import('../pages/home.vue'),
meta: {
title: 'home'
}
}
```
把导入写到外边
```js
const Home = () => import('../pages/home.vue')
// ...
{
path: '/',
component: Home,
meta: {
title: 'home'
}
}
```
### 组件按组分块
把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用命名 chunk
```js
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
```