VueRouter基本原理及实战

看了本篇你能收获什么?

  • 如何显示多层级的树形菜单?嵌套路由是唯一的实现方式吗?
  • 如何规避嵌套路由使用KeepAvlie缓存失效的问题?
  • vue2的路由和vue3在使用上有哪些区别?
  • 哈希模式和history模式有什么区别?哈希模式就一定是使用hashchange吗?

一、路由简介

什么是路由?

通过监听浏览器url的改变,导航到页面中的不同视图,从而实现切换页面又不用重新加载html的效果。在实现单页面应用(SPA,single page application)的后台系统中,路由基本上是必不可少的。

VueRouter的模式

  • 哈希模式,HashHistory mode。
  • history模式,Html5History mode。
  • 抽象模式,AbstractHistory mode。

哈希模式,HashHistory Mode

它在内部传递的实际 URL 之前使用了一个哈希字符(#,如https://example.com/#/)。由于这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理。不过,它在 SEO 中确实有不好的影响。如果你担心这个问题,可以使用 HTML5 模式。

为什么哈希路由对SEO不友好?

因为#号后面的内容是由前端处理的,爬虫规则不会对这一部分进行任何处理。
比如https://example.com/#/foohttps://example.com/#/bar这两个页面在浏览器中虽然是展示两个不同的页面,但是在爬虫规则中,就只会识别到https://example.com/#/而已。
开发过微信公众号H5页面的同学都知道,使用微信jssdk生成签名时,需要传一个url,它也是不能包含#号的,个人认为也是同一个原因。

不过,既然都开发SPA应用了,还在乎那点SEO?即使使用history模式,爬虫读到了不同的页面,拿到的也是客户端未渲染的模板字符串,没什么意义。

历史模式,Html5History mode

该模式的URL 会看起来很 “正常”,例如https://example.com/user/id
它出现的比哈希模式晚,是伴随着HTML5中新增的pushStatereplaceStateAPI才出现的,所以不支持HTML5的浏览器无法使用。

不过这点兼容基本可以忽略,HTML5规范是2014年时出的,现在基本不可能脱离HTML5特性开发的。

pushStateAPI比较好的一点是,传递参数是通过stateObject,这个对象中可以包含任意类型。而哈希模式由于是通过url传参的,不仅长度会受到限制,类型也只能是字符串。

history模式下,假设qqq是一个不存在的路由,在开发模式下表现为不渲染当前页面组件而已。但是如果打包部署到服务器后,用户在浏览器中直接访问 localhost/qqq,就会得到一个 404 错误。
VueRouter基本原理及实战
要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。

例如,我在nginx配置中添加了,try_files $uri $uri/ /index.html;,这样就会回退到index页面。
当然,你也可以自定义一个404页面,这样子既具有提示性,也会更美观。

VueRouter基本原理及实战

抽象模式,AbstractHistory mode

用于非浏览器环境的环境下,使用一个虚拟的历史记录。比如服务端渲染,原生的跨平台应用。
该模式可以手动开启,或者当前环境为非浏览器时会自动开启。

VueRouter初始化源码概览

在我们执行new VueRouter的时候,大概会经历以下流程:

  • 如果mode值为空,则默认开启哈希模式
  • 如果mode值为history,但是当前浏览器环境不支持html5特性,则回退到哈希模式
  • 如果当前为非浏览器环境,则强制回退到抽象模式
  • 如果输入的值不在三种模式中,比如输入了mode: aabb,则抛出【无效模式】的报错

VueRouter基本原理及实战

哈希模式监听事件

浏览器中存在一个事件hashchange,当url中#号后面的部分被改变时,就会执行。按照我们正常的思维,哈希模式就是使用hashchange,history模式才会使用pushState等API。
但是源码中只要支持pushState事件,就不会再用hashchange了。
VueRouter基本原理及实战
这里我查了很多资料,都没有很明确的答案解释为什么要这么做。看到之前有一个issue,也有人提了相同的疑惑,尤大在下面只是回复了特殊处理的方案,也没提到具体的原因。
如果知道原因的小伙伴,欢迎交流~
VueRouter基本原理及实战

二、VueRouter实战

vue2和vue3中使用区别

这里更准确的描述是VueRouter3.x和VueRouter4.x版本的使用区别,这两个版本分别对应的是vue2.x和vue3.x。

调用方式

vue2 vue3
跳转页面 this.$router.push({name: 'xxx'}) import { useRouter } from 'vue-router'
const router = useRouter()
router.push({name: 'xxx'})
查看路由信息 console.log(this.$route) import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route)

一对比起来,vue3整个使用的链路长了很多。在vue3中,先引入再声明最后才能调用,比vue2多了两步前置的。

其他区别

  • Vue Router 不再是一个类,而是一组函数。
  • 移动了base属性的配置。
  • 删除了fallback属性。前面解析源码时提到hash模式下,如果支持pushState就用pushState否则才用hashchange。这一版则是直接移除了hashchange这个回退方案,因为vue3不再兼容太低版本的浏览器了。
// vue2
import VueRouter from 'vue-router'
new VueRouter({
  base: '/myApp', // 非必填,只有服务器有额外配置才会使用
  mode: 'history', // 对应hash模式和history模式
  fallback: true, // vue3中已移除
})

// vue3
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
const router = createRouter({
  // createWebHashHistory和createWebHistory对应hash模式和history模式
  history: createWebHistory('/myApp'),
});

  • transition 和 keep-alive 现在必须通过 v-slot API 在 RouterView 内部使用。
// transition 和 keep-alive 现在必须通过 v-slot API 在 RouterView 内部使用。
<router-view v-slot="{ Component }">
  <transition>
    <keep-alive>
      <component :is="Component" />
    </keep-alive>
  </transition>
</router-view>

上述变更只是部分常见的内容,更多细节可以参考官方的对比文档。router.vuejs.org/zh/guide/mi…

路由匹配

这里主要针对以下 3 种类型的路由进行举例:

  • 静态路由
  • 动态路由
  • 嵌套路由

静态路由

这个是最基础的路由,结构代码为:

const routes = [
  {
    path: '/home',
    component: () => import('页面组件'),
  },
];

当浏览器的url为https://example.com/home时,他就会显示出来。

动态路由

比静态路由多了一个路径参数,该参数使用:xxx这种格式来表示。

const routes = [
  {
    path: '/home/:userId ',
    component: () => import('页面组件'),
  },
];

和query方式的对比

当我们跳转路由时,如果希望刷新页面,还能保留路由参数。可以有以下 2 种做法:

  • router.push({ path: '/home', query: { userId: '123' } }),通过route.query取值
  • router.push({ name: 'home', params: { userId: '123' } }),通过route.params取值

既然作用一样,为什么还要多增加一个动态路由?
这里查到的比较有道理的一种说法是,动态路由的意义更多是用来标识资源的唯一性。并且在vue3中,声明了动态路由,就一定要传参,否则就会直接报错。

// 给与以下路由:
const routes = [{ path: '/home/:id', name: 'home', component: xxx }]

// 缺少 `id` 参数会失败
router.push({ name: 'home' })
router.resolve({ name: 'home' })

嵌套路由

嵌套也很好理解,就是一个router-view的组件中,还可以包着router-view
假设有一个根节点,里面是第一层路由

<div id="app">
  <router-view></router-view>
</div>

这时候有个组件User,里面还有层路由

// 跳转到的路由为User
const routes = [{ path: '/user/:id', component: User }]

// 第二层里面还有个router-view
const User = {
  template: `
    <div class="user">
      <h2>User {{ $route.params.id }}</h2>
      <router-view></router-view>
    </div>
  `,
}

最终渲染出来的结构就是这样:

<div id="app">
  <router-view>
  	<router-view></router-view>
  </router-view>
</div>

要将组件渲染到这个嵌套的 router-view 中,我们需要在路由中配置 children:

const routes = [
  {
    path: '/user',
    component: User,
    children: [
      {
        // 当 /user/profile 匹配成功
        // UserProfile 将被渲染到 User 的 <router-view> 内部
        path: 'profile',
        component: UserProfile,
      },
    ],
  },
]

如何显示多层级菜单?

假设现在有一个菜单为 3 个层级,如何设计他的路由结构呢?
VueRouter基本原理及实战
看到这个树形结构,是不是第一时间就会嵌套路由?毕竟它也是一个树形结构。那对应导航栏的路由结构就可以写成:

const routes = [
  {
    title: '列表页', 
    path: '/list',
    component: () => import('xxx'),
    children: [
      {
        title: '搜索列表', 
        path: 'search',
        component: () => import('xxx'),
        children: [
          {
            title: '搜索列表(文章)', 
            path: 'article',
            component: () => import('xxx'),
          },
        ]
      },
    ]
  },
];

直接把路由组件递归一下,就可以得到下面这个html结构,最里面那层的组件也可以显示了,完美!

<router-view>
  <router-view>
    <router-view></router-view>
  </router-view>
</router-view>

但是有一天,突然来了个需求,要求打开过的页面不允许重新加载,而是使用缓存。正常来说,我们直接使用KeepAlive组件即可。

<router-view v-slot="{ Component }">
  <keep-alive>
    <component :is="Component" />
  </keep-alive>
</router-view>

不过KeepAlive只能保活他包裹的那一层路由,在嵌套组件中,就需要特殊处理了。
以antd pro vue的框架为例,左边为导航栏,右边为路由显示页面。无论导航栏的层级有多深,右边也是只显示一个router-view就够了,完全用不到嵌套路由的特性。
我觉得嵌套路由的使用场景,就只有在保留父路由的部分内容,同时又在里面渲染一层子路由的时候才会用到。
VueRouter基本原理及实战
那处理方式也很简单,写个函数将导航栏中的路由结构拍平即可。

antd pro框架多页签问题

使用过antd pro框架的同学都知道,按照它默认的路由配置,渲染的菜单层级超过 3 层就会有问题。下面是它官方文档的截图,只是建议路由不要超过 3 级,或者把KeepAlive关闭。
VueRouter基本原理及实战
但是实际场景中,超过 3 级的菜单非常常见。而使用拍平路由结构的方式,就能完美解决这个问题。对于修改antd pro这种封装得比较多的框架来说,不需要大改它原有的组件层级,只需要调整路由数据,这是改动成本最低的方式。

参考

原文链接:https://juejin.cn/post/7240666488336285752 作者:专业逮虾户aaa

(0)
上一篇 2023年6月4日 上午11:13
下一篇 2023年6月5日 上午10:05

相关推荐

发表回复

登录后才能评论