从 Vue 2 迁移
Vue Router 的大部分 API 在从 v3(用于 Vue 2)到 v4(用于 Vue 3)的重写过程中保持不变,但仍然存在一些您在迁移应用程序时可能会遇到的重大更改。本指南旨在帮助您了解这些更改的原因以及如何调整您的应用程序以使其与 Vue Router 4 兼容。
重大更改
更改按其使用顺序排列。因此建议按照此列表的顺序进行操作。
new Router 变为 createRouter
Vue Router 不再是一个类,而是一组函数。您现在需要调用 createRouter
,而不是编写 new Router()
// previously was
// import Router from 'vue-router'
import { createRouter } from 'vue-router'
const router = createRouter({
// ...
})
新的 history
选项替换 mode
mode: 'history'
选项已被更灵活的选项 history
替换。根据您使用的模式,您需要用相应的函数替换它
"history"
:createWebHistory()
"hash"
:createWebHashHistory()
"abstract"
:createMemoryHistory()
以下是一个完整的代码片段
import { createRouter, createWebHistory } from 'vue-router'
// there is also createWebHashHistory and createMemoryHistory
createRouter({
history: createWebHistory(),
routes: [],
})
在 SSR 上,您需要手动传递适当的历史记录
// router.js
let history = isServer ? createMemoryHistory() : createWebHistory()
let router = createRouter({ routes, history })
// somewhere in your server-entry.js
router.push(req.url) // request url
router.isReady().then(() => {
// resolve the request
})
原因:启用未使用的历史记录的树状摇动,以及为高级用例(如原生解决方案)实现自定义历史记录。
移动了 base
选项
base
选项现在作为第一个参数传递给 createWebHistory
(和其他历史记录)
import { createRouter, createWebHistory } from 'vue-router'
createRouter({
history: createWebHistory('/base-directory/'),
routes: [],
})
删除了 fallback
选项
创建路由器时不再支持 fallback
选项
-new VueRouter({
+createRouter({
- fallback: false,
// other options...
})
原因:Vue 支持的所有浏览器都支持 HTML5 History API,允许我们避免修改 location.hash
的 hack,并直接使用 history.pushState()
。
删除了 *
(星号或通配符)路由
通配符路由(*
,/*
)现在必须使用带自定义正则表达式的参数进行定义
const routes = [
// pathMatch is the name of the param, e.g., going to /not/found yields
// { params: { pathMatch: ['not', 'found'] }}
// this is thanks to the last *, meaning repeated params and it is necessary if you
// plan on directly navigating to the not-found route using its name
{ path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound },
// if you omit the last `*`, the `/` character in params will be encoded when resolving or pushing
{ path: '/:pathMatch(.*)', name: 'bad-not-found', component: NotFound },
]
// bad example if using named routes:
router.resolve({
name: 'bad-not-found',
params: { pathMatch: 'not/found' },
}).href // '/not%2Ffound'
// good example:
router.resolve({
name: 'not-found',
params: { pathMatch: ['not', 'found'] },
}).href // '/not/found'
提示
如果您不打算使用其名称直接推送到未找到的路由,则无需为重复的参数添加 *
。如果您调用 router.push('/not/found/url')
,它将提供正确的 pathMatch
参数。
原因:Vue Router 不再使用 path-to-regexp
,而是实现了自己的解析系统,该系统允许路由排名并启用动态路由。由于我们通常每个项目只添加一个通配符路由,因此支持 *
的特殊语法没有太大好处。参数的编码在所有路由中都是一致的,没有例外,以使事情更容易预测。
currentRoute
属性现在是一个 ref()
以前,路由实例上的 currentRoute
对象的属性可以直接访问。
随着 Vue Router v4 的引入,路由实例上的 currentRoute
对象的底层类型已更改为 Ref<RouteLocationNormalizedLoaded>
,它来自 Vue 3 中引入的较新的 响应式基础。
虽然如果您使用 useRoute()
或 this.$route
读取路由,这不会改变任何内容,但如果您直接在路由实例上访问它,则需要通过 currentRoute.value
访问实际的路由对象
const { page } = router.currentRoute.query
const { page } = router.currentRoute.value.query
用 isReady
替换 onReady
现有的 router.onReady()
函数已被 router.isReady()
替换,它不接受任何参数并返回一个 Promise
// replace
router.onReady(onSuccess, onError)
// with
router.isReady().then(onSuccess).catch(onError)
// or use await:
try {
await router.isReady()
// onSuccess
} catch (err) {
// onError
}
scrollBehavior
更改
scrollBehavior
中返回的对象现在类似于 ScrollToOptions
:x
重命名为 left
,y
重命名为 top
。请参阅 RFC。
原因:使对象类似于 ScrollToOptions
,使其与原生 JS API 更加熟悉,并可能启用未来的新选项。
<router-view>
、<keep-alive>
和 <transition>
transition
和 keep-alive
现在必须通过 v-slot
API 在 RouterView
内部使用
<router-view v-slot="{ Component }">
<transition>
<keep-alive>
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
原因:这是必要的更改。请参阅 相关 RFC。
删除了 <router-link>
中的 append
属性
append
属性已从 <router-link>
中删除。您可以手动将该值连接到现有的 path
replace
<router-link to="child-route" append>to relative child</router-link>
with
<router-link :to="append($route.path, 'child-route')">
to relative child
</router-link>
您必须在 App 实例上定义一个全局 append
函数
app.config.globalProperties.append = (path, pathToAppend) =>
path + (path.endsWith('/') ? '' : '/') + pathToAppend
原因:append
并不经常使用,很容易在用户空间中复制。
删除了 <router-link>
中的 event
和 tag
属性
event
和 tag
属性都已从 <router-link>
中删除。您可以使用 v-slot
API 来完全自定义 <router-link>
replace
<router-link to="/about" tag="span" event="dblclick">About Us</router-link>
with
<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 出现之前就引入了,使用频率不足以证明为每个人添加捆绑包大小的理由。
删除了 <router-link>
中的 exact
属性
exact
属性已删除,因为修复其问题的注意事项不再存在,因此您应该可以安全地删除它。但是,您应该注意两件事
- 路由现在根据它们所代表的路由记录而不是生成的路由位置对象及其
path
、query
和hash
属性来激活 - 只有
path
部分匹配,query
和hash
不再被考虑
如果您希望自定义此行为,例如考虑 hash
部分,您应该使用 v-slot
API 来扩展 <router-link>
。
原因:请参阅 关于活动匹配的 RFC 更改以了解更多详细信息。
混合中的导航守卫被忽略
目前,混合中的导航守卫不受支持。您可以在 vue-router#454 中跟踪其支持情况。
删除了 router.match
并更改了 router.resolve
router.match
和 router.resolve
都已合并到 router.resolve
中,并具有略微不同的签名。有关更多详细信息,请参阅 API。
原因:统一用于相同目的的多个方法。
移除 router.getMatchedComponents()
方法 router.getMatchedComponents
现已移除,因为匹配组件可以从 router.currentRoute.value.matched
中获取。
router.currentRoute.value.matched.flatMap(record =>
Object.values(record.components)
)
原因:此方法仅在 SSR 期间使用,并且用户可以轻松地用一行代码实现。
重定向记录不能使用特殊路径
以前,一个未记录的功能允许将重定向记录设置为特殊路径,例如 /events/:id
,它将重用现有的参数 id
。这现在不再可能,有两种选择:
- 使用路由名称,不带参数:
redirect: { name: 'events' }
。请注意,如果参数:id
是可选的,则此方法无效。 - 使用函数根据目标重新创建新位置:
redirect: to => ({ name: 'events', params: to.params })
原因:此语法很少使用,并且是一种另一种方式,与上面的版本相比,它不够简洁,同时引入了复杂性,并使路由器更重。
所有导航现在始终是异步的
所有导航,包括第一次导航,现在都是异步的,这意味着,如果您使用 transition
,您可能需要等待路由器准备好才能挂载应用程序。
app.use(router)
// Note: on Server Side, you need to manually push the initial location
router.isReady().then(() => app.mount('#app'))
否则,将会有一个初始过渡,就像您为 transition
提供了 appear
属性一样,因为路由器显示其初始位置(无)然后显示第一个位置。
请注意,如果您在初始导航时有导航守卫,您可能不想阻止应用程序渲染,直到它们被解析,除非您正在进行服务器端渲染。在这种情况下,不等待路由器准备好挂载应用程序将产生与 Vue 2 中相同的结果。
移除 router.app
router.app
用于表示注入路由器的最后一个根组件(Vue 实例)。Vue Router 现在可以安全地同时被多个 Vue 应用程序使用。您仍然可以在使用路由器时添加它。
app.use(router)
router.app = app
您还可以扩展 Router
接口的 TypeScript 定义以添加 app
属性。
原因:Vue 3 应用程序不存在于 Vue 2 中,现在我们正确地支持使用同一个路由器实例的多个应用程序,因此拥有 app
属性会产生误导,因为它将是应用程序而不是根实例。
将内容传递给路由组件的 <slot>
以前,您可以通过将模板嵌套在 <router-view>
组件下,直接将模板传递给路由组件的 <slot>
以进行渲染。
<router-view>
<p>In Vue Router 3, I render inside the route component</p>
</router-view>
由于引入了 v-slot
API 用于 <router-view>
,您必须使用 v-slot
API 将其传递给 <component>
。
<router-view v-slot="{ Component }">
<component :is="Component">
<p>In Vue Router 3, I render inside the route component</p>
</component>
</router-view>
移除路由位置中的 parent
parent
属性已从规范化的路由位置(this.$route
和 router.resolve
返回的对象)中移除。您仍然可以通过 matched
数组访问它。
const parent = this.$route.matched[this.$route.matched.length - 2]
原因:拥有 parent
和 children
会创建不必要的循环引用,而这些属性可以通过 matched
获取。
移除 pathToRegexpOptions
路由记录的 pathToRegexpOptions
和 caseSensitive
属性已被 createRouter()
的 sensitive
和 strict
选项替换。它们现在也可以在使用 createRouter()
创建路由器时直接传递。任何其他特定于 path-to-regexp
的选项已被移除,因为 path-to-regexp
不再用于解析路径。
移除未命名参数
由于移除了 path-to-regexp
,未命名参数不再受支持。
/foo(/foo)?/suffix
变成/foo/:_(foo)?/suffix
/foo(foo)?
变成/foo:_(foo)?
/foo/(.*)
变成/foo/:_(.*)
提示
请注意,您可以使用任何名称代替 _
作为参数。关键是提供一个名称。
使用 history.state
Vue Router 在 history.state
上保存信息。如果您有任何代码手动调用 history.pushState()
,您应该避免它或使用常规的 router.push()
和 history.replaceState()
重构它。
// replace
history.pushState(myState, '', url)
// with
await router.push(url)
history.replaceState({ ...history.state, ...myState }, '')
类似地,如果您在不保留当前状态的情况下调用 history.replaceState()
,您需要传递当前的 history.state
。
// replace
history.replaceState({}, '', url)
// with
history.replaceState(history.state, '', url)
原因:我们使用历史状态保存有关导航的信息,例如滚动位置、上一个位置等。
routes
选项在 options
中是必需的
属性 routes
现在在 options
中是必需的。
createRouter({ routes: [] })
原因:路由器被设计为使用路由创建,即使您可以在以后添加它们。在大多数情况下,您至少需要一条路由,并且这通常在每个应用程序中只写一次。
不存在的命名路由
推送或解析不存在的命名路由会抛出错误。
// Oops, we made a typo in name
router.push({ name: 'homee' }) // throws
router.resolve({ name: 'homee' }) // throws
原因:以前,路由器会导航到 /
,但不会显示任何内容(而不是主页)。抛出错误更有意义,因为我们无法生成有效的 URL 来导航。
命名路由缺少必需的 params
推送或解析没有其必需参数的命名路由将抛出错误。
// given the following route:
const routes = [{ path: '/users/:id', name: 'user', component: UserDetails }]
// Missing the `id` param will fail
router.push({ name: 'user' })
router.resolve({ name: 'user' })
原因:与上面相同。
具有空 path
的命名子路由不再追加斜杠
给定任何具有空 path
的嵌套命名路由。
const routes = [
{
path: '/dashboard',
name: 'dashboard-parent',
component: DashboardParent,
children: [
{ path: '', name: 'dashboard', component: DashboardDefault },
{
path: 'settings',
name: 'dashboard-settings',
component: DashboardSettings,
},
],
},
]
导航或解析到命名路由 dashboard
现在将生成一个没有尾部斜杠的 URL。
router.resolve({ name: 'dashboard' }).href // '/dashboard'
这对像这样的子 redirect
记录有重要的副作用。
const routes = [
{
path: '/parent',
component: Parent,
children: [
// this would now redirect to `/home` instead of `/parent/home`
{ path: '', redirect: 'home' },
{ path: 'home', component: Home },
],
},
]
请注意,如果 path
是 /parent/
,则 home
相对于 /parent/
的相对位置确实是 /parent/home
,但 home
相对于 /parent
的相对位置是 /home
。
原因:这是为了使尾部斜杠行为一致:默认情况下,所有路由都允许尾部斜杠。可以使用 strict
选项禁用它,并手动追加(或不追加)斜杠到路由。
$route
属性编码
params
、query
和 hash
中的解码值现在始终一致,无论导航从哪里开始(旧版浏览器仍然会生成未编码的 path
和 fullPath
)。初始导航应该产生与应用程序内导航相同的结果。
给定任何 规范化的路由位置
path
、fullPath
中的值不再解码。它们将以浏览器提供的形式出现(大多数浏览器提供编码后的形式)。例如,直接在地址栏中写入https://example.com/hello world
将产生编码后的版本:https://example.com/hello%20world
,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 })
不会创建额外的编码。
TypeScript 更改
为了使类型更一致和更具表现力,一些类型已被重命名。
vue-router@3 | vue-router@4 |
---|---|
RouteConfig | RouteRecordRaw |
Location | RouteLocation |
Route | RouteLocationNormalized |
新功能
Vue Router 4 中的一些新功能值得关注: