Vue Router v3.x 迁移到 v4.x(两者的区别)【路由篇】

文章目录

  • 前言
  • 一、迁移背景
  • 二、开始迁移
    • [2.1 破坏性变化](#2.1 破坏性变化)
    • [2.2 新的 history 配置取代 mode](#2.2 新的 history 配置取代 mode)
    • [2.3 移动了 base 配置](#2.3 移动了 base 配置)
    • [2.4 删除了 fallback 属性](#2.4 删除了 fallback 属性)
    • [2.5 删除了 *(星标或通配符)路由](#2.5 删除了 *(星标或通配符)路由)
    • [2.6 现在 currentRoute 属性是一个 ref()](#2.6 现在 currentRoute 属性是一个 ref())
    • [2.7 将 onReady 改为 isReady](#2.7 将 onReady 改为 isReady)
    • [2.8 scrollBehavior 的变化](#2.8 scrollBehavior 的变化)
    • [2.9 router-view 、 keep-alive 和 transition](#2.9 router-view 、 keep-alive 和 transition)
    • [2.10 删除 router-link 中的 append 属性](#2.10 删除 router-link 中的 append 属性)
    • [2.11 删除 router-link 中的 event 和 tag 属性](#2.11 删除 router-link 中的 event 和 tag 属性)
    • [2.12 删除 router-link 中的 exact 属性](#2.12 删除 router-link 中的 exact 属性)
    • [2.13 忽略 mixins 中的导航守卫](#2.13 忽略 mixins 中的导航守卫)
    • [2.14 删除 router.match 改为 router.resolve](#2.14 删除 router.match 改为 router.resolve)
    • [2.15 删除 router.getMatchedComponents()](#2.15 删除 router.getMatchedComponents())
    • [2.16 重定向记录不能使用特殊路径](#2.16 重定向记录不能使用特殊路径)
    • [2.17 所有的导航现在都是异步的](#2.17 所有的导航现在都是异步的)
    • [2.18 删除 router.app](#2.18 删除 router.app)
    • [2.19 将内容传递给路由组件的 slot](#2.19 将内容传递给路由组件的 slot)
    • [2.20 将 parent 从路由地址中删除](#2.20 将 parent 从路由地址中删除)
    • [2.21 删除 pathToRegexpOptions](#2.21 删除 pathToRegexpOptions)
    • [2.22 删除未命名的参数](#2.22 删除未命名的参数)
    • [2.23 history.state 的用法](#2.23 history.state 的用法)
    • [2.24 options 中需要配置 routes](#2.24 options 中需要配置 routes)
    • [2.25 不存在的命名路由](#2.25 不存在的命名路由)
    • [2.26 命名路由缺少必要的 params](#2.26 命名路由缺少必要的 params)
    • [2.27 带有空 path 的命名子路由不再添加斜线](#2.27 带有空 path 的命名子路由不再添加斜线)
    • [2.28 route 属性编码](#2.28 route 属性编码)
    • [2.29 TypeScript 变化](#2.29 TypeScript 变化)
    • 三、总结

前言

Vue Router 是 Vue.js (opens new window)官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。这里为什么要分 v3.x 和 v4.x 两篇来写的,主要还是两者在项目使用中着实有不小的差距。

💯本人在 基于 Vue2 的老项目和基于 Vue3 的新项目上在路由这块确实懵逼过。在 Vue2 项目上安装路由组件的时候经常会安装 v4.x 版本,然后就各种问题。 所以我们先从 v3.x 版本学习,然后再对比 v4.x 版本看看都有哪些差异。

🎯提示: 一般没人去做升级了,权当学习了 v3.x 和 v4.x 两者的差异性了。


一、迁移背景

在 Vue Router API 从 v3(Vue2)到 v4(Vue3)的重写过程中,大部分的 Vue Router API 都没有变化,但是在迁移你的程序时,你可能会遇到一些破坏性的变化。本指南将帮助你了解为什么会发生这些变化,以及如何调整你的程序,使其与 Vue Router4 兼容。

二、开始迁移

2.1 破坏性变化

变化的顺序是按其用途排列的。因此,建议按照这个清单的顺序进行。

🎯注意: new Router 变成 createRouter
Vue Router 不再是一个类,而是一组 函数 。现在你不用再写 new Router(),而是要调用 createRouter:

js 复制代码
// 以前是
// import Router from 'vue-router'
import { createRouter } from 'vue-router'

const router = createRouter({
  // ...
})

2.2 新的 history 配置取代 mode

mode: 'history' 配置已经被一个更灵活的 history 配置所取代。根据你使用的模式,你必须用适当的函数替换它:

  • "history": createWebHistory()
  • "hash": createWebHashHistory()
  • "abstract": createMemoryHistory()

下面是一个完整的代码段:

js 复制代码
import { createRouter, createWebHistory } from 'vue-router'
// 还有 createWebHashHistory 和 createMemoryHistory

createRouter({
  history: createWebHistory(),
  routes: [],
})

在 SSR 上使用时,你需要手动传递相应的 history:

js 复制代码
// router.js
let history = isServer ? createMemoryHistory() : createWebHistory()
let router = createRouter({ routes, history })
// 在你的 server-entry.js 中的某个地方
router.push(req.url) // 请求 url
router.isReady().then(() => {
  // 处理请求
})

🎗️原因: 为未使用的 history 启用摇树,以及为高级用例(如原生解决方案)实现自定义 history。

2.3 移动了 base 配置

现在,base 配置被作为 createWebHistory (其他 history 也一样)的第一个参数传递:

js 复制代码
import { createRouter, createWebHistory } from 'vue-router'
createRouter({
  history: createWebHistory('/base-directory/'),
  routes: [],
})

2.4 删除了 fallback 属性

创建路由时不再支持 fallback 属性:

bash 复制代码
-new VueRouter({
+createRouter({
-  fallback: false,
// other options...
})

📑原因: Vue 支持的所有浏览器都支持 HTML5 History API,因此我们不再需要使用 location.hash,而可以直接使用 history.pushState()。

2.5 删除了 *(星标或通配符)路由

现在必须使用自定义的 regex 参数来定义所有路由(*、/*):

js 复制代码
const routes = [
  // pathMatch 是参数的名称,例如,跳转到 /not/found 会得到
  // { params: { pathMatch: ['not', 'found'] } }
  // 这要归功于最后一个 *,意思是重复的参数,如果你
  // 打算直接使用未匹配的路径名称导航到该路径,这是必要的
  { path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound },
  // 如果你省略了最后的 `*`,在解析或跳转时,参数中的 `/` 字符将被编码
  { path: '/:pathMatch(.*)', name: 'bad-not-found', component: NotFound },
]
// 如果使用命名路由,不好的例子:
router.resolve({
  name: 'bad-not-found',
  params: { pathMatch: 'not/found' },
}).href // '/not%2Ffound'
// 好的例子:
router.resolve({
  name: 'not-found',
  params: { pathMatch: ['not', 'found'] },
}).href // '/not/found'

🔖TIP:

如果你不打算使用其名称直接跳转到未找到的路由,则无需为重复的参数添加 *。如果你调用 router.push('/not/found/url'),它将提供正确的 pathMatch 参数。

🎗️原因: Vue Router 不再使用 path-to-regexp,而是实现了自己的解析系统,允许路由排序并实现动态路由。由于我们通常在每个项目中只添加一个通配符路由,所以支持 * 的特殊语法并没有太大的好处。参数的编码是跨路由的,无一例外,让事情更容易预测。

2.6 现在 currentRoute 属性是一个 ref()

路由器实例上的 currentRoute 对象的属性在以前可以被直接访问。

从 vue-router v4 开始,路由器实例上的 currentRoute 对象的底层类型已被改为 Ref<RouteLocationNormalizedLoaded>,其源自 Vue 3 中新引入的响应式基础。

当你使用 useRoute() 或 this.$route 获取路由信息时这并不会带来任何变化,如果想要直接在路由器实例上访问它,你需要通过 currentRoute.value 来访问实际的路由对象:

js 复制代码
- const { page } = router.currentRoute.query
+ const { page } = router.currentRoute.value.query

2.7 将 onReady 改为 isReady

现有的 router.onReady() 函数已被 router.isReady() 取代,该函数不接受任何参数并返回一个 Promise:

js 复制代码
// 将
router.onReady(onSuccess, onError)
// 替换成
router.isReady().then(onSuccess).catch(onError)
// 或者使用 await:
try {
  await router.isReady()
  // 成功
} catch (err) {
  // 报错
}

2.8 scrollBehavior 的变化

scrollBehavior 中返回的对象与 ScrollToOptions 类似:x 改名为 left,y 改名为 top。详见 RFC。

原因:使该对象类似于 ScrollToOptions,以使其感觉更像原生 JS API,并有可能启用将来的新配置。

2.9 router-view 、 keep-alive 和 transition

transition 和 keep-alive 现在必须通过 v-slot API 在 RouterView 内部使用:

html 复制代码
<router-view v-slot="{ Component }">
  <transition>
    <keep-alive>
      <component :is="Component" />
    </keep-alive>
  </transition>
</router-view>

原因: 这是一个必要的变化。详见 related RFC.

<router-link> 中的 append 属性已被删除。你可以手动将值设置到现有的 path 中:

html 复制代码
将
<router-link to="child-route" append>to relative child</router-link>
替换成
<router-link :to="append($route.path, 'child-route')">
  to relative child
</router-link>

你必须在 App 实例上定义一个全局的 append 函数:

js 复制代码
app.config.globalProperties.append = (path, pathToAppend) =>
  path + (path.endsWith('/') ? '' : '/') + pathToAppend

原因:append 使用频率不高,用户可以很容易地实现。

<router-link> 中的 event 和 tag 属性都已被删除。你可以使用 v-slot API 来完全定制 <router-link>:

html 复制代码
将
<router-link to="/about" tag="span" event="dblclick">About Us</router-link>
替换成
<router-link to="/about" custom v-slot="{ navigate }">
  <span @click="navigate" @keypress.enter="navigate" role="link">About Us</span>
</router-link>

原因:这些属性经常一起使用,以使用与 <a> 标签不同的东西,但这些属性是在 v-slot API 之前引入的,并且没有足够的使用,因此没有足够的理由为每个人增加 bundle 包的大小。

exact 属性已被删除,因为不再存在要修复的警告,所以你应该能够安全地删除它。但,有两件事你应该注意:

路由现在是基于它们所代表的路由记录来激活的,而不是路由地址对象及其 path、query 和 hash 属性来激活的

只匹配 path 部分,query 和 hash 不再考虑

如果你想自定义这种行为,例如考虑到 hash 部分,你应该使用 v-slot API 来扩展 <router-link> 。

原因: 详见 RFC about active matching。

2.13 忽略 mixins 中的导航守卫

目前不支持 mixins 中的导航守卫,你可以在 vue-router#454 追踪它的支持情况。

2.14 删除 router.match 改为 router.resolve

router.match 和 router.resolve 已合并到 router.resolve 中,签名略有不同。详见 API。

原因:将用于同一目的的多种方法统一起来。

2.15 删除 router.getMatchedComponents()

router.getMatchedComponents 方法现在 被删除 ,因为匹配的组件可以从 router.currentRoute.value.matched 中获取:

js 复制代码
router.currentRoute.value.matched.flatMap(record =>
  Object.values(record.components)
)

原因:这个方法只在 SSR 中使用,并且是用户一行就能完成的操作。

2.16 重定向记录不能使用特殊路径

以前,有一个未被记录的特性允许将重定向记录设置为形如 /events/:id 的特殊路径,并且它会复用现有的参数 id。现在它已不再支持,有两个替代选项:

  • 使用不包含参数的路由名称:redirect: { name: 'events' }。注意,当参数 :id 为可选项时它不会生效
  • 使用一个函数重建基于目标的新位置:redirect: to => ({ name: 'events', params: to.params })

原因:这种语法很少使用,是上述版本的等效做法,但是相比之下还不够简短,同时会引入一些复杂性,使路由器变得更重。

2.17 所有的导航现在都是异步的

所有的导航,包括第一个导航,现在都是异步的,这意味着,如果你使用一个 transition,你可能需要等待路由 ready 好后再挂载程序:

js 复制代码
app.use(router)
// 注意:在服务器端,你需要手动跳转到初始地址。
router.isReady().then(() => app.mount('#app'))

否则会有一个初始过渡,就像你提供了 appear 属性到 transition 一样,因为路由会显示它的初始地址(什么都没有),然后显示第一个地址。

请注意,如果在初始导航时有导航守卫,你可能不想阻止程序渲染,直到它们被解析,除非你正在进行服务器端渲染。否则,在这种情况下,不等待路由准备好挂载应用会产生与 Vue2 中相同的结果。

2.18 删除 router.app

router.app 用于表示注入路由的最后一个根组件(Vue 实例)。Vue Router 现在可以被多个 Vue 程序同时安全使用。你仍然可以在使用路由时添加它:

js 复制代码
app.use(router)
router.app = app

你也可以扩展 Router 接口的 TypeScript 定义来添加 app 属性。

原因:Vue3 写的程序不能在 Vue2 中使用,现在我们使用同一个 Router 实例来支持多个程序,因此拥有 app 属性可能会产生误导,因为它是程序而不是根实例。

2.19 将内容传递给路由组件的 slot

之前你可以直接传递一个模板,通过嵌套在 <router-view> 组件下,由路由组件的 <slot> 来渲染:

html 复制代码
<router-view>
  <p>In Vue Router 3, I render inside the route component</p>
</router-view>

由于 <router-view> 引入了 v-slot API,你必须使用 v-slot API 将其传递给 <component>:

html 复制代码
<router-view v-slot="{ Component }">
  <component :is="Component">
    <p>In Vue Router 3, I render inside the route component</p>
  </component>
</router-view>

2.20 将 parent 从路由地址中删除

parent 属性已从标准化路由地址(this.$route 和 router.resolve 返回的对象)中删除。你仍然可以通过 matched 数组访问它:

js 复制代码
const parent = this.$route.matched[this.$route.matched.length - 2]

原因: 同时存在 parent 和 children 会造成不必要的循环引用,而属性可以通过 matched 来检索。

2.21 删除 pathToRegexpOptions

路由的 pathToRegexpOptions 和 caseSensitive 属性已被 createRouter() 的 sensitive 和 strict 配置取代。现在,当使用 createRouter() 创建路由时,它们也可以直接传递。path-to-regexp 的任何其他特定配置已被删除,因为 path-to-regexp 已不再用于解析路径。

2.22 删除未命名的参数

由于取消了 path-to-regexp,所以不再支持未命名的参数:

  • /foo(/foo)?/suffix 变成 /foo/:_(foo)?/suffix
  • /foo(foo)? 变成 /foo:_(foo)?
  • /foo/(.) 变成 /foo/:_(.)

请注意,你可以使用任何名称代替 _ 作为参数。重点是要提供一个名字。

2.23 history.state 的用法

Vue Router 将信息保存在 history.state 上。如果你有任何手动调用 history.pushState() 的代码,你应该避免它,或者用的 router.push() 和 history.replaceState() 进行重构:

js 复制代码
// 将
history.pushState(myState, '', url)
// 替换成
await router.push(url)
history.replaceState({ ...history.state, ...myState }, '')

同样,如果你在调用 history.replaceState() 时没有保留当前状态,你需要传递当前 history.state:

js 复制代码
// 将
history.replaceState({}, '', url)
// 替换成
history.replaceState(history.state, '', url)

原因:我们使用历史状态来保存导航信息,如滚动位置,以前的地址等。

2.24 options 中需要配置 routes

options 中的 routes 属性现在是必需的。

js 复制代码
createRouter({ routes: [] })

原因:路由的设计是为了创建路由,尽管你可以在以后添加它们。在大多数情况下,你至少需要一条路由,一般每个应用都会编写一次。

2.25 不存在的命名路由

跳转或解析不存在的命名路由会产生错误:

js 复制代码
// 哎呀,我们的名字打错了
router.push({ name: 'homee' }) // 报错
router.resolve({ name: 'homee' }) // 报错

原因:以前,路由会导航到 /,但不显示任何内容(而不是主页)。抛出一个错误更有意义,因为我们不能生成一个有效的 URL 进行导航

2.26 命名路由缺少必要的 params

在没有传递所需参数的情况下跳转或解析命名路由,会产生错误:

js 复制代码
// 给与以下路由:
const routes = [{ path: '/users/:id', name: 'user', component: UserDetails }]

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

2.27 带有空 path 的命名子路由不再添加斜线

给予任何空 path 的嵌套命名路由:

js 复制代码
const routes = [
  {
    path: '/dashboard',
    name: 'dashboard-parent',
    component: DashboardParent,
    children: [
      { path: '', name: 'dashboard', component: DashboardDefault },
      {
        path: 'settings',
        name: 'dashboard-settings',
        component: DashboardSettings,
      },
    ],
  },
]

现在,导航或解析到命名的路由 dashboard 时,会产生一个不带斜线的 URL:

js 复制代码
router.resolve({ name: 'dashboard' }).href // '/dashboard'

这对子级 redirect 有重要的副作用,如下所示:

js 复制代码
const routes = [
  {
    path: '/parent',
    component: Parent,
    children: [
      // 现在将重定向到 `/home` 而不是 `/parent/home`
      { path: '', redirect: 'home' },
      { path: 'home', component: Home },
    ],
  },
]

请注意,如果 path 是 /parent/,这也可以,因为 home 到 /parent/ 的相对地址确实是 /parent/home,但 home 到 /parent 的相对地址是 /home。

原因:这是为了使尾部的斜线行为保持一致:默认情况下,所有路由都允许使用尾部的斜线。可以通过使用 strict 配置和手动添加(或不添加)斜线来禁用它。

2.28 $route 属性编码

无论在哪里启动导航,params、query、 hash 中的解码值现在都是一致的(旧的浏览器仍然会产生未编码的 path 和 fullPath)。初始导航应产生与应用内部导航相同的结果。

给定任何规范化的路由地址:

  • path, fullPath中的值不再被解码了。例如,直接在地址栏上写 "https://example.com/hello world",将得到编码后的版本:"https://example.com/hello world",而 "path "和 "fullPath "都是"/hello%20world"。
  • hash 现在被解码了,这样就可以复制过来。router.push({ hash: $route.hash }) 可以直接用于 scrollBehavior 的 el 配置中。
  • 当使用 push、resolve 和 replace 并在对象中提供 string 地址或 path 属性时,必须进行编码(像以前的版本一样)。另一方面,params、query 和 hash 必须以未编码的版本提供。
  • 斜线字符(/)现在已在 params 内正确解码,同时仍在 URL 上产生一个编码版本:%2F。

原因: 这样,在调用 router.push() 和 router.resolve() 时,可以很容易地复制一个地址的现有属性,并使产生的路由地址在各浏览器之间保持一致。router.push() 现在是幂等的,这意味着调用 router.push(route.fullPath)、router.push({ hash: route.hash })、router.push({ query: route.query }) 和router.push({ params: route.params }) 不会产生额外的编码。

2.29 TypeScript 变化

为了使类型更一致,更有表现力,有些类型被重新命名:

vue-router@3 vue-router@4
RouteConfig RouteRecordRaw
Location RouteLocation
Route RouteLocationNormalized

三、总结

其实 Vue2 项目基本和 Vue3 项目彻底是无法升级了,所以路由这块也没必要去迁移了,权当学习了它俩的区别的。


相关推荐
集智飞行16 分钟前
解决mavros2 ros2版本cpu占用高的问题
java·服务器·前端
w2sfot1 小时前
【JS加密在线】jsjiami.online(让JavaScript 更安全!)
开发语言·javascript·安全
HSunR1 小时前
ruoyi 若依 自定义注解 参数校验
java·前端·数据库
知野小兔1 小时前
Vuex和Pinia状态管理
vue.js
拆房老料2 小时前
ONLYOFFICE也能像Microsoft Word和WPS一样分别设置中西文字体
前端·html·word·开源软件·wps
VX_bysjlw9852 小时前
数码设备销售网站设计与实现39138-计算机毕设原创(免费领源码+带部署教程)
java·vue.js·spring boot·mysql·tomcat·mybatis·idea
Solis程序员2 小时前
Stripe 幂等表:把「不确定的重试」变成「查表返回」
前端·安全·bootstrap·php·agent
李纲明3 小时前
WordPress 站点变慢:按 TTFB → 缓存 → 查询 → 前端 分层排查(含配置注意)
前端·缓存·性能优化·wordpress·后端开发
程序人生8883 小时前
PDF 转 Word 版式错乱、表格丢失?DocConverter Web 格式互转引擎的保真实践
前端·人工智能·opencv·机器学习·pdf·word
粥里有勺糖4 小时前
安利一下最近用的桌面“Agent” | T3 Code
前端·github·ai编程