vue学习记录之vue路由组件vue-router
前言
在使用前端框架之前链接其他页面我们可以使用<a>标签来实现,但是使用了vue等框架后,我们的源码都是各种.vue文件,自然无法再使用<a>标签进行跳转,如果要想使用<a>标签实现则必须知道编译后的路径+名称,这种虽然能做到但是太过费事。为此Vue官方为我们开发者提供了vue-router来实现页面跳转,它的作用就跟<a>标签类似。
安装
CDN使用
https://unpkg.com/vue-router@4
npm安装
npm i vue-router@4
一般在使用
vue-cli创建项目时勾选vue-router自动会生成示例代码.
快速开始
需要用到路由的页面
<template>
  <router-link to="/"/>
  <!-- 对于命名路由还可以通过名称跳转 -->
  <router-link to="{name: 'home'}"/>
  <router-link to="/hello"/>
  
  <!-- 通过此标签展示跳转后的页面内容 -->
  <router-view/>
</template>
对路由进行配置的JavaScript
import {createRouter, createWebHistory} from 'vue-router';
import HelloWorld from "@/components/HelloWorld";
const routes = [
    {
        name: 'Home', // 设置了name属性的路由被称为命名路由
        path: '/',
        // 路由懒加载(推荐)
        component: () => {
            import('@/components/Home')
        }
    },
    {
        name: 'HelloWorld',
        path: 'hello',
        // 常规加载
        component: HelloWorld
    }
];
const router = createRouter({
    history: createWebHistory(process.env.BASE_URL),
    routes,
})
export default router;
把上面这个配置在vue对象中用上.
import { createApp } from 'vue'
import App from './App.vue'
// @是src的别名
import router from "@/router";
const app =  createApp(App)
app.use(router)
app.mount('#app')
基础
路由参数获取
可以使用$route.query或者$route.params来获取。
- $route.query中存放的是- URL中的参数(- /api/user?userId=123)
- $route.params中存放的是- URL中的路径参数(- /api/user/123,- 123即为路径参数)
路由跳转
此处的路由跳转指的是通过
JavaScript的方式实现跳转。
路由跳转可以使用$router.push()函数实现,$router中还提供了其他的实用函数,例如:$router.go()、$router.replace()等。
以下几个API就是模仿的window.history的API,用的时候不会有太多陌生感。
- $router.push({path: '', query: {}})
- $router.go(1)在历史记录中前进,与- $router.forward()等价
- $router.go(-1)在历史记录中后退,与- $router.back()等价
- $router.push({path: '/home', replace: true})替换当前位置(不会记录在历史记录中),它等价于- $router.replace({path: '/home'})。
// !!! 需要注意的是
$router.push({path: '', params: {}})
// 这种情况下params会被忽略,它们两个不能用在一起。可以使用下面两种替代
// 方式1
$router.push({name: 'home', params: {user: 123}})
// 方式2
$router.push({path: `/home/${user}`})
动态路由
SpringWebMVC中有@RequestMapping支持路径参数、正则语法,vue-router中支持这样的使用。
const routes = [
  // 动态段以冒号开始
  { path: '/users/:id', component: UserDemo },
]
这个路由能匹配到/users/abc、/users/1234等,其中abc、1234可以通过$route.params.id获取到。一个路由上还可以设置多个路径参数。
| 路由 | 匹配路径 | $route.params | 
|---|---|---|
| /users/:id | /user/abc | {id:abc} | 
| /users/:id/:type | /user/abc/1234 | {id:abc,type:1234} | 
使用带有参数的路由时需要注意的是,当用户从
/users/johnny导航到/users/jolyne时,相同的组件实例将被重复使用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会被调用。
404路由
const routes = [
  // 将匹配所有内容并将其放在$route.params.pathMatch下
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }
]
高级匹配模式
一般使用静态路由/abc和动态路由/:abc就够我们用了,但vue-router还为我们提供了正则匹配模式。
自定义正则
const routes = [
  // /:orderId -> 仅匹配数字
  { path: '/:orderId(\\d+)' },
  // /:productName -> 匹配其他任何内容
  { path: '/:productName' },
]
可重复参数
如果要匹配多级路由比如/abc/1234/xyz可以使用*(将参数重复0次或多次)和+(将参数重复1次或多次)。
const routes = [
  // /:chapters ->  匹配 /one, /one/two, /one/two/three, 等
  { path: '/:chapters+' },
  // /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
  { path: '/:chapters*' },
  // 仅匹配数字
  // 匹配 /1, /1/2, 等
  { path: '/:chapters(\\d+)+' },
  // 匹配 /, /1, /1/2, 等
  { path: '/:chapters(\\d+)*' },
]
此时如果使用
$router.push进行跳转需要传入数组类型参数方可。$router.push({name: 'home', params: {chapters: ['one', 'two']}})
可选参数
如果要想让一个参数可传可不传则可以使用?(0次或1次)。
const routes = [
  // 匹配 /users 和 /users/posva
  { path: '/users/:userId?' },
  // 匹配 /users 和 /users/42
  { path: '/users/:userId(\\d+)?' },
]
*号虽然也可以做到,但它与?的区别是?参数只能用0或者1次,而*则还可以让参数用多次。
嵌套路由
有些项目中会存在一些嵌套组件,比如用户页面是由个人信息、设置、收藏等组件组成的,而这些组件假设也有对应的路由,并且点击后是在用户这个组件中展示(类似于手风琴式的UI),这种路由就属于嵌套路由。
对于嵌套路由vue-router提供了children属性来配置:
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      // 当 /user/:id 匹配成功
      // UserHome 将被渲染到 User 的 <router-view> 内部
      { path: '', component: UserHome },
      {
        // 当 /user/:id/profile 匹配成功
        // UserProfile 将被渲染到 User 的 <router-view> 内部
        path: 'profile',
        component: UserProfile,
      },
      {
        // 当 /user/:id/posts 匹配成功
        // UserPosts 将被渲染到 User 的 <router-view> 内部
        path: 'posts',
        component: UserPosts,
      },
    ],
  },
]
<!-- User Component -->
<template>
  ...
  <router-view/>
</template>
命名视图
如果想同时展示多个视图,并且不是嵌套展示,这时候就需要命名视图了。
router-view默认的name属性值为default,多个视图就可以用多个router-view并设置上name属性(必须否则将会展示同样的内容)。
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
const routes = [
    {
      path: '/',
      components: {
        default: Home,
        // LeftSidebar: LeftSidebar 的缩写
        LeftSidebar,
        // 它们与 `<router-view>` 上的 `name` 属性匹配
        RightSidebar,
      },
    },
  ],
嵌套命名视图同理,在需要嵌套的组件中使用router-view组件,并在嵌套路由中使用命名视图即可。
重定向和别名
重定向
在使用redirect时可以不用写component,因为它是直接通过重定向后的配置访问的组件,这里可以不配置,除非是嵌套路由。
// path的方式
const router = [{path: "/abc", redirect: "/xyz"}]
// name的方式
const router = [{path: "/abc", redirect: {name: "demo"}}]
//方法的形式
const router = [{
    path: "/abc/:s", redirect: to => {
        // to和$route是一样的对象类型
        return {path: "/s", query: {q: to.params.s}}
    }
}]
别名
重定向是指当用户访问/home时,URL 会被/替换,然后匹配成/。那么什么是别名呢?
将/别名为/home,意味着当用户访问/home时,URL 仍然是/home,但会被匹配为用户正在访问/。
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
嵌套路由中使用别名可以使用绝对路径来改变这个路由的路径
const routes = [
  {
    path: '/users',
    component: UsersLayout,
    children: [
      // 为这 3 个 URL 呈现 UserList
      // - /users
      // - /users/list
      // - /people
      { path: '', component: UserList, alias: ['/people', 'list'] },
    ],
  },
]
如果路由中有参数,那么绝对路径的别名路由必须包含此参数
const routes = [
  {
    path: '/users/:id',
    component: UsersByIdLayout,
    children: [
      // 为这 3 个 URL 呈现 UserDetails
      // - /users/24
      // - /users/24/profile
      // - /24
      { path: 'profile', component: UserDetails, alias: ['/:id', ''] },
    ],
  },
]
路由组件传参
vue-router提供了另一种传参给组件的方法,那就是props属性。props拥有布尔模式、对象模式、函数模式。
- 布尔模式
props为true时$router.params将被设置为组件的props
const routes = [{ path: '/user/:id', component: User, props: true }]
// 命名视图必须为为每个视图定义props的值
const routes = [
  {
    path: '/user/:id',
    components: { default: User, sidebar: Sidebar },
    props: { default: true, sidebar: false }
  }
]
- 对象模式
props是对象时,内容会原样设置到组件的props中。
const routes = [
  {
    path: '/user/profile',
    component: User,
    props: { isLogin: true }
  }
]
- 函数模式
也可以是一个函数,参数为$route类型,返回值将被设置到组件的props中。
const routes = [
  {
    path: '/user',
    component: User,
    props: route => ({ name: route.query.abc })
  }
]
/user?abc=lisi这个URL将传递{name: 'lisi'}给User组件。
历史模式
创建路由时demo使用的历史模式是HTML5模式,除此还有Hash模式。
- HTML5模式
这种模式对SEO很友好,URL看上去与正常的URL没什么区别。
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
  history: createWebHistory(),
  routes: [],
})
但如果用户直接访问http://demo.com/user/123会是404错误,这需要在服务器配置一个会退路由来解决,让其为匹配到URL时找到你程序的index.html。
以下仅提供Nginx的配置,其他服务器请看官方文档。
location / {
  try_files $uri $uri/ /index.html;
}
- Hash模式
Hash模式会在URL中用哈希字符#隔开实际上用到的URL,这种模式与常规URL不同,因此对SEO不是很友好。
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
  history: createWebHashHistory(),
  routes: [],
})
进阶内容
导航守卫
全局前置守卫
const router = createRouter({ ... })
router.beforeEach((to, from) => {
  // ...
  // 返回 false 以取消导航
  return false
})
- to目标路由对象
- from来源路由对象
- 可以返回false来取消导航,也可以返回一个路由地址(可以是一个字符串或路由对象,返回的内容跟router.push()中可以用的参数是一样的)
除此支持旧版的第三个参数next
router.beforeEach((to, from, next) => {
    if (to.name !== 'Login' && !isAuthenticated) {
        next({name: 'Login'})
    } else {
        next()
    }
})
全局解析守卫
router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})
全局后置钩子
router.afterEach((to, from) => {
  console.log(to.fullPath)
})
// 没有next参数,只有failure,在此钩子无法终止导航
router.afterEach((to, from, failure) => {
  if(failure){
    console.log(to.fullPath);
  }
})
这三个守卫/钩子跟拦截器的效果差不多,顺序就是按命名来理解。
路由级导航守卫
此导航守卫只在进入路由时才会触发,不会在params、query、hash变更时触发。
const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // 返回值与全局前置导航守卫一样
      return true;
    },
  },
]
组件内导航守卫
提供三个可用的API:
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
export default {
  name: 'Demo',
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例`this`,因为当守卫执行时,组件实例还没被创建!
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 比如用的是一个带有动态参数的路径 `/users/:id`,参数变化组件会复用
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}
在beforeRouteEnter中虽然无法直接使用this访问实例,但是可以用next参数,在导航确认后调用此回调函数访问实例。
beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}
完整的导航流程
- 导航被触发。
- 在失活的组件里调用beforeRouteLeave守卫。
- 调用全局的beforeEach守卫。
- 在重用的组件里调用beforeRouteUpdate守卫(2.2+)。
- 在路由配置里调用beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用beforeRouteEnter。
- 调用全局的beforeResolve守卫(2.5+)。
- 导航被确认。
- 调用全局的afterEach钩子。
- 触发DOM更新。
- 调用beforeRouteEnter守卫中传给next的回调函数,创建好的组件实例会作为回调函数的参数传入。
数据元信息
const routes = [
    {
        path: '/',
        name: 'Default',
        component: () => import('@/views/home/Home'),
        meta: {
            title: '首页',
            needAuth: false
        }
    }
];
router.beforeEach((to, from) => {
    // 设置每个路由页面的标题
    document.title = to.meta.title;
    return true;
})