vue-router

  • vue-router已关闭评论
  • 238 次浏览
  • A+
所属分类:Web前端
摘要

vue-router是一个vue的插件,用来实现前端的路由, 推荐使用 pnpm add vue-router@4 进行安装。推荐配合vue3组合式api使用


安装

vue-router是一个vue的插件,用来实现前端的路由, 推荐使用 pnpm add vue-router@4 进行安装。推荐配合vue3组合式api使用

基础

从一个例子开始

<!-- App.vue文件 --> <div id="app">   <h1>Hello App!</h1>   <p>     <!--使用 router-link 组件进行导航 -->     <!--通过传递 `to` 来指定链接 -->     <!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->     <router-link to="/hello/112233">hello</router-link>   </p>   <!-- 路由出口 -->   <!-- 路由匹配到的组件将渲染在这里 -->   <router-view></router-view> </div> 
// router/index.js 文件  //createRouter 用来创建路由 //createWebHashHistory 指定路由是hash模式  import { createRouter,createWebHashHistory } from 'vue-router'  import Hello from '../components/Hello.vue' import ErrorPage from '../components/ErrorPage.vue'  // 配置路由规则 const routers = [     // 匹配所有路由(正则匹配权重低),自定义404     {path: '/:all(.*)*', component: ErrorPage},     {path: '/hello/:id',component: Hello} ]  // 导出路由 export default createRouter({     history: createWebHashHistory(),    // 这里我们使用简单的hash模式     routes: routers }) 
// main.js 文件 import { createApp } from 'vue' import './style.css' import App from './App.vue' import routers from './routers'  createApp(App)     .use(routers)		// 挂载路由     .mount('#app') 

项目中的路由对象

tips: 别忘记挂载路由哦 如 use(router)

  1. $router: 通过this调用,或者通过 import导出路由也一样,此路由对象,就是导出的路由对象,可以调用push等 ... <-
  2. $route: 通过this调用,此路由对象是当前的路由对象,比如说 /Home 拿到的是当前 /Home 的路由对象,可以通过 $route.params 拿到路由参数等

动态路由

匹配规则

匹配模式 匹配路径 匹配说明 $route.params $route.query $route.hash
/users/:username /users/eduardo?sid=123#ok ok
/users/:username/posts/:postId /users/eduardo/posts/123?sid=123#ok ok
/:pathMatch(.*)* /a/b 匹配所有
/user-:afterUser(.*) /user-admin 匹配user- 以开头的
/:orderId(\d+) /123 匹配数字
/:chapters+ /a/a/a 匹配重复的(一个或多个)

从上面可以看出,路由匹配是支持自定义正则的,官方推荐的路由调试工具 https://paths.esm.dev/

Sensitive 与 strict 路由配置

const router = createRouter({   history: createWebHistory(),   routes: [     {          // /users	ok         // /Users	error         // /user/	error         path: '/users',          sensitive: true,	// 大小写敏感         strict: true, 		// 是否严格按照path匹配              },   ],   strict: true,  // 或者这里设置全局 }) 

嵌套路由

比如我们有两个路由 /home/user,/home/posts。可以看出来他们都是处于 /home下的,属于是嵌套关系,我们可以使用以下代码实现

const routes = [   {     path: '/home',     component: Home,     children: [			// home的子级       {         path: 'user',         component: User,       },       {         path: 'posts',         component: Posts,       },     ],   }, ] 

编程式导航

顶文的有个例子使用 <router-link to="/home">点我啊</router-link> 进行路由导航,其实点击 <router-link :to="..."> 相当于调用 router.push(...)

编程时导航就是通过js代码来跳转路由,通过以下例子介绍下

// 字符串路径 router.push('/users/admin')  // 带有路径的对象 router.push({ path: '/users/admin' })  // 命名的路由,让路由建立 url, 可以传递params, query,hash, 传递的参数vue-router会自动编码,如果放在path中需要先编码一下 router.push({ name: 'user', params: { username: 'admin' }, query: { plan: '花儿为什么这么红' }, hash: '#h1' } })   
  • push: 这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL
  • replace: 会替代当前位置,无法回退,其余的同上
  • forward: 向前移动一条记录
  • back: 向后移动一条记录
  • go: go(-1)后退,反之亦然

命名路由

除了 path 之外,你还可以为任何路由提供 name。这有以下优点:

  • 没有硬编码的 URL
  • params 的自动编码/解码。
  • 防止你在 url 中出现打字错误。
const routes = [   {     path: '/user/:username',     name: 'user',     component: User,   }, ] 
router.push({ name: 'user', params: { username: 'any' } } 

命名视图

有时候我们想要在同一个页面里展示多个视图,又不想嵌套展示,例如创建一个布局,sidebar(侧边栏),main(主要区域),示例代码如下。当然也可以嵌套命名视图,

<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 router = createRouter({   history: createWebHashHistory(),   routes: [     {       path: '/',       components: {         default: Home,         // LeftSidebar: LeftSidebar 的缩写         LeftSidebar,         // 它们与 `<router-view>` 上的 `name` 属性匹配         RightSidebar,       },     },   ], })   // 或使用嵌套命名视图  {   path: '/settings',   // 你也可以在顶级路由就配置命名视图   component: UserSettings,   children: [{     path: 'emails',     component: UserEmailsSubscriptions   }, {     path: 'profile',     components: {       default: UserProfile,       helper: UserProfilePreview     }   }] } 

重定向和别名

 // redirect可以是一个字符串,一个对象,一个函数。示例如下 const routes = [   {     // /search/screens -> /search?q=screens     path: '/search/:searchText',     redirect: to => {       // 方法接收目标路由作为参数       // return 重定向的字符串路径/路径对象       return { path: '/search', query: { q: to.params.searchText } }     },   },   {     path: '/search',     // ...   }, ] 
// 别名,就是说当访问这个别名的时候,跳转到此路由,不受路由限制。推荐少用少用少用!!!  const routes = [     {         path: '/home',         alias: ['/',‘’]		// 也可以设置两个别名     } ]  

给组件传递props

我们可以通过$route拿到参数,但是这样写的话组件过于依赖路由,我们有更好的一种方式,通过设置 props: true 开启props传参, 示例如下

const routes = [   {     path: '/user/:id',     components: { default: User, sidebar: Sidebar },          // 有以下几种写法            // 1:props: true     // 2:props: { default: true, sidebar: false }     // 3:{ id: '传入的参数' }	     // 4: route=> ({ id: route.params.id })   } ] 

路由模式

Hash 模式

在浏览器url使用了哈希字符 # 这部分url没有被发送服务器。 在SEO优化 有不好的影响,如果担心这个问题可以使用HTML5模式

import { createRouter, createWebHashHistory } from 'vue-router'  const router = createRouter({   history: createWebHashHistory(),   routes: [     //...   ], }) 

HTML5 模式

这种模式看起来像一个比较正常的url 比如 https://example.com/home/user 。 需要在服务器配置

import { createRouter, createWebHistory } from 'vue-router'  const router = createRouter({   history: createWebHistory(),   routes: [     //...   ], }) 

服务器配置

  1. nginx

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

    const http = require('http') const fs = require('fs') const httpPort = 80  http   .createServer((req, res) => {     fs.readFile('index.html', 'utf-8', (err, content) => {       if (err) {         console.log('We cannot open "index.html" file.')       }        res.writeHead(200, {         'Content-Type': 'text/html; charset=utf-8',       })        res.end(content)     })   })   .listen(httpPort, () => {     console.log('Server listening on: http://localhost:%s', httpPort)   }) 
    1. iis

      <?xml version="1.0" encoding="UTF-8"?> <configuration>   <system.webServer>     <rewrite>       <rules>         <rule name="Handle History Mode and custom 404/500" stopProcessing="true">           <match url="(.*)" />           <conditions logicalGrouping="MatchAll">             <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />             <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />           </conditions>           <action type="Rewrite" url="/" />         </rule>       </rules>     </rewrite>   </system.webServer> </configuration> 

进阶

好啦,看到这已经理解基础了,想了解更的话继续往下看,我们有时候需要做点别的 事情

导航守卫

导航守卫就是AOP的思想,在路由进入前进入后更新,做点事情。导航守卫可以使用多个会依次执行,类似于管道,路由匹配的组件会复用。 它可以挂载到全局单个路由单个组件。各有妙用,望君细品。代码示例如下

全局守卫

// 进入前 router.beforeEach((to, from)=>{     //返回 false 以取消导航,也可以返回一个对象,比如 {name:'home'},相当于重定向 })  // 或者加入第三个参数 next,如果声明了第三个参数,则必须调用一个,否则会一直等待 router.beforeEach((to, from, next)=>{          // next(false) 或者 next({path: '/home'}) })  // 进入后 router.afterEach((to, from)=>{      })  // 解析守卫,发生在beforeEach后 router.beforeResolve((to, from)=>{      })    

路由专用守卫

// 进入路由触发(但是还没有进入,在beforeResolve前触发)   function removeQueryParams(to) {   if (Object.keys(to.query).length)     return { path: to.path, query: {}, hash: to.hash } }  function removeHash(to) {   if (to.hash) return { path: to.path, query: to.query, hash: '' } }   const routes = [   {     path: '/users/:id',     component: UserDetails,     beforeEnter: [removeQueryParams, removeHash],	// 可以是数组,按顺序调用   }, ] 

组件内的守卫

// 选项式: beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave // 组合式:onBeforeRouteUpdate,onBeforeRouteLeave   const UserDetails = {   template: `...`,   beforeRouteEnter(to, from) {     // 在渲染该组件的对应路由被验证前调用     // 不能获取组件实例 `this` !     // 因为当守卫执行时,组件实例还没被创建!   },   beforeRouteUpdate(to, from) {     // 在当前路由改变,但是该组件被复用时调用     // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,     // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。     // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`   },   beforeRouteLeave(to, from) {     // 在导航离开渲染该组件的对应路由时调用     // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`   }, } 

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫(2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

路由元信息

有时候想要将信息附加到路由上,我们看下面这个例子

const routes = [   {     path: '/posts',     component: PostsLayout,     children: [       {         path: 'new',         component: PostsNew,         // 只有经过身份验证的用户才能创建帖子         meta: { requiresAuth: true }       },       {         path: ':id',         component: PostsDetail         // 任何人都可以阅读文章         meta: { requiresAuth: false }       }     ]   } ] 
// 我们可以通过 $route.meta 拿到meta数据  router.beforeEach((to, from) => {   // 而不是去检查每条路由记录   // to.matched.some(record => record.meta.requiresAuth)   if (to.meta.requiresAuth && !auth.isLoggedIn()) {     // 此路由需要授权,请检查是否已登录     // 如果没有,则重定向到登录页面     return {       path: '/login',       // 保存我们所在的位置,以便以后再来       query: { redirect: to.fullPath },     }   } }) 

在组合式API的应用

在组合式API公开下面两个导航守卫API

import { onBeforeRouteUpdate,onBeforeRouteLeave } from 'vue-router'

onBeforeRouteLeave: 即将离开路由

onBeforeRouteUpdate: 路由的hash或者query发生了变化

自定义router-link

 <template>     <div @click="handle">     	<slot></slot>     </div> </template>   <script>  import { RouterLink, useLink } from 'vue-router' import { computed } from 'vue'  export default {  name: 'AppLink', props: {  // 如果使用 TypeScript,请添加 @ts-ignore  ...RouterLink.props,  inactiveClass: String, }, methods:{   handle(){    this.$router.push(this.to)  } }, setup(props) {  	const {        // 解析出来的路由对象        route,        // 用在链接里的 href        href,        // 布尔类型的 ref 标识链接是否匹配当前路由        isActive,        // 布尔类型的 ref 标识链接是否严格匹配当前路由        isExactActive,        // 导航至该链接的函数        navigate        } = useLink(props)   	const isExternalLink = computed(   	 () => typeof props.to === 'string' && props.to.startsWith('http')  	)   	return { isExternalLink, href, navigate, isActive } 	 	}, }  </script> 

过度动效

顾名思义就是给路由的切换添加动画效果

例子如下

<router-view v-slot="{ Component, route }">   <transition name="fade">     <component :is="Component" :key="route.path" />   </transition> </router-view> 

我们用v-slot拿到了 component和route,然后通过动态组件的方式来显示,在transition指定动画效果

由于vue会自动复用看起来类似的组件,所有我们需要加上唯一 key


滚动行为

这个比较简单就是一个 api

例子如下

const router = createRouter({  history: createWebHashHistory(),  routes: [...],  scrollBehavior (to, from, savedPosition) {    // return 期望滚动到哪个的位置,示例如下  } })  /* 返回值支持以下,也可以返回promise,支持延迟滚动  return {      // 也可以这么写      // el: document.getElementById('main'),      // el: to.hash,			 定位锚点      // behavior: 'smooth',	 滚动流畅            el: '#main',      top: -10, } */  

如果返回一个 falsy(非真值) 的值,或者是一个空对象,那么不会发生滚动。

返回 savedPosition,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:


路由懒加载

vue-router支持动态导入我们这样写,示例如下

const router = createRouter({  routes: [      {           path: '/users/:id',           component: () => import('./views/UserDetails.vue')       }  ], }) 

为了避免请求过多,我们有时候需要组件按组分块,示例如下

// 在路由下配置webpackChunkName,vite支持webpack的这种写法。同名的设置为一组  const UserDetails = () =>  import(/* webpackChunkName: "group-user" */ './UserDetails.vue') const UserDashboard = () =>  import(/* webpackChunkName: "group-user" */ './UserDashboard.vue') const UserProfileEdit = () =>  import(/* webpackChunkName: "group-user" */ './UserProfileEdit.vue')     // 我们在vite.config.js 配置  export default defineConfig({  build: {    rollupOptions: {      // https://rollupjs.org/guide/en/#outputmanualchunks      output: {        manualChunks: {          'group-user': [				// group-user组            './src/UserDetails.vue',            './src/UserDashboard.vue',            './src/UserProfileEdit.vue',          ],        },      },    },  }, })   // 配置后将会在build后 才能生效!!!!  

扩展router-link

在router-link组件上封装一层(自定义 RouterLink 组件),示例如下

<template>  <a v-if="isExternalLink" v-bind="$attrs" :href="to" target="_blank">          <slot />  </a>  <router-link    v-else    v-bind="$props"    custom    v-slot="{ isActive, href, navigate }">          <a      v-bind="$attrs"      :href="href"      @click="navigate"      :class="isActive ? activeClass : inactiveClass">              <slot />    </a>  </router-link> </template>  <script> import { RouterLink } from 'vue-router'  export default {  name: 'AppLink',  inheritAttrs: false,   props: {    // 如果使用 TypeScript,请添加 @ts-ignore    ...RouterLink.props,    inactiveClass: String,  },   computed: {    isExternalLink() {      return typeof this.to === 'string' && this.to.startsWith('http')    },  }, } </script> 

使用组件

<AppLink    v-bind="$attrs"    class="inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"    active-class="border-indigo-500 text-gray-900 focus:border-indigo-700"    inactive-class="text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300"  >    点我点我 </AppLink> 

路由导航检测

有时候我们需要在push后做点什么,可以在 push 前面加上 await 示例如下

通过 push等方法进行路由导航,有时候未必会成功跳转,比如已经在目标路由,或者路由返回了false,示例如下

 // push返回的是一个promise,因此我们可以这样写 await $router.push('/user/list') this.isMenuOpen = false;  // 判断是否导航成功  var navigationResult = await $router.push('/user/list'); if (navigationResult) {   // 导航被阻止 } else {   // 导航成功 (包括重新导航的情况)   this.isMenuOpen = false }  // 其实通过navigationResult对象可以拿到详细的错误信息,这个用的比较少,这里就不写了  

动态路由

对于已经运行的程序,我们想添加路由,就看这里,动态路由主要通过两个函数实现 router.addRoute()router.removeRoute()

 var removeRoute = router.addRoute({path: '/user', name: 'user', component: xxx }) // router.replace(router.currentRoute.value.fullPath)	// 然后手动调用replace覆盖当前路由   // 可以覆盖替换,当名字一样的情况下 removeRoute('填入路由的名字')	// 调用它的返回值可以删掉路由  // 添加嵌套路由 router.addRoute('父路由的名字', {path: '/user', name: 'user', component: xxx })  // 查看现有路由  router.hasRoute()	// 检查路由是否存在 router.getRoutes()	// 获取一个包含所有路由记录的数组 

扩展

unplugin-vue-router

基于文件的自动路由,可以使用vite提供的功能(vite-plugin-pages)

也可以通过官方提供的一个插件 unplugin-vue-router

emmmm... 这个内容有点多,还是看官网吧...

GitHub unplugin-vue-router


持续学习

阅读 vue-router@4 API

详细见官网

拜了个拜~