For Vue3
yarn add vue-router@4
创建一个Hash模式的最简单路由并应用:
import {createApp} from 'vue';
import VueRouter from 'vue-router';
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
})
const app = createApp(App);
app
.use(router)
.use(store)
.mount('#app');
使用createWebHashHistory()
创建Hash模式,使用createWebHistory()
创建HTML5模式,并应用在createRouter
的history
选项
推荐使用HTML5模式,但是需要在服务器上添加回退路由,配置实例参考官网。
要注意,如果使用HTML5模式,vue.config.js
中的publicPath
需要配置为绝对路径'/'
,不应该配置为相对路径,否则会出现资源找不到的情况!
在动态匹配路由时,如果有两个路径相同的动态路由,可以再括号中为参数指定自定义正则,例如为orderId
指定数字匹配:
const routes = [
// /:orderId -> 仅匹配数字
{ path: '/:orderId(\\d+)' },
// /:productName -> 匹配其他任何内容
{ path: '/:productName' },
]
这样,/25
就会匹配第/:orderId
,其他情况会匹配/:productName
,路由的定义顺序不重要,因为自定义正则有着更高的优先级
要主要确保转义\
如果有需要匹配多个部分的路由,例如/first/second/third
,使用*
(0个或多个)和+
(一个或多个)将参数标记为可重复
const routes = [
// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等
{ path: '/:chapters+' },
// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
{ path: '/:chapters*' },
]
这将提供一个参数数组,使用命名路由时也需要传递数组。同时也可以通过在右括号天津嘉它们与自定义正则结合使用
const routes = [
// 仅匹配数字
// 匹配 /1, /1/2, 等
{ path: '/:chapters(\\d+)+' },
// 匹配 /, /1, /1/2, 等
{ path: '/:chapters(\\d+)*' },
]
使用?
来将一个参数标记为可选:
const routes = [
// 匹配 /users 和 /users/posva
{ path: '/users/:userId?' },
// 匹配 /users 和 /users/42
{ path: '/users/:userId(\\d+)?' },
]
如果希望同级展示多个视图,在同一个组件中存在多个
,这个时候就可以使用命名视图,例如:
在配置时,多个视图需要配置多个组件在components
选项上,key
值为
的name
:
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `` 上的 `name` 属性匹配
RightSidebar,
},
},
],
})
利用命名视图也可以创建嵌套视图的复杂布局,例如下面的例子:
User Settings
Nav
只是一个常规组件。
UserSettings
是一个视图组件。
UserEmailsSubscriptions
、UserProfile
、UserProfilePreview
是嵌套的视图组件。
配置时:
{
path: '/settings',
// 你也可以在顶级路由就配置命名视图
component: UserSettings,
children: [{
path: 'emails',
component: UserEmailsSubscriptions
}, {
path: 'profile',
components: {
default: UserProfile,
helper: UserProfilePreview
}
}]
}
重定向的目标除了路径和命名路由外,还可以是一个方法,这个方法接受目标路由作为参数,动态返回重定向目标
const routes = [
{
// /search/screens -> /search?q=screens
path: '/search/:searchText',
redirect: to => {
// 方法接收当前路由作为参数
// return 重定向的字符串路径/路径对象
return { path: '/search', query: { q: to.params.searchText } }
},
},
{
path: '/search',
// ...
},
]
注意,导航守卫并不会应用在跳转路由上,只会应用在其跳转的目标路由上
alias
可以接收数组,可以接受绝对路径从而避免嵌套结构的限制,如果路由有参数,确保在别命中包含参数
const routes = [
{
path: '/users',
component: UsersLayout,
children: [
// 为这 3 个 URL 呈现 UserList
// - /users
// - /users/list
// - /people
// - /book/123
{ path: '', component: UserList, alias: ['/people', 'list', '/book/:id'] },
],
},
]
把路由参数作为组件的Props传递以组件,避免组件与Route耦合,有三种模式:
(1)布尔模式
将props
设置为true
,route.params
将被设置为组件的Props
const User = {
props: ['id'],
template: 'User {
{ id }}'
}
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 }
}
]
(2)对象模式
当props
是一个对象,会将对象中的属性设置为组件的Props,适合于Props为静态的情况
const routes = [
{
path: '/promotion/from-newsletter',
component: Promotion,
props: { newsletterPopup: false }
}
]
(3)函数模式
props
可以是一个函数,接受当前路由作为参数,返回一个对象作为组件的Props
const routes = [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
导航守卫可以返回值:
false
,取消当前导航router.push
一样Error
,取消导航,并调用router.onError
注册的回调undefined
或true
,导航有效,调用下一个导航守卫router.beforeEach(async (to, from) => {
// canUserAccess() 返回 `true` 或 `false`
return await canUserAccess(to)
})
next
是第三个参数,仍然被支持,但是根据上面的描述,完全可以不通过next
完成导航,使用next
经常会出现调用多次或者不被调用的问题,而通过返回值来确定导航是否有效会确保戴航始终有效
通过扩展RouteMeta
接口来输入meta
字段:
// typings.d.ts or router.ts
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
// 是可选的
isAdmin?: boolean
// 每个路由都必须声明
requiresAuth: boolean
}
}
setup
中访问路由和当前路由在setup
中不能使用this
,所以需要引入useRouter
和useRoute
函数代替this.$router
和this.$route
route
对象是一个响应式对象,属性都可以监听,但是应该避免监听整个route
对象以提升性能:
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute()
const userData = ref()
// 当参数更改时获取用户信息
watch(
() => route.params,
async newParams => {
userData.value = await fetchUser(newParams.id)
}
)
},
}
可以通过导入onBeforeRouteLeave
和onBeforeRouteUpdate
两个函数,来使用组件内的导航守卫。
可以用在任何由
渲染的组件中,不必像组件内守卫那样直接用在路由组件上
需要使用v-slot
API
可以将路由的元信息和动态name
结合在一起,放在
上
const routes = [
{
path: '/custom-transition',
component: PanelLeft,
meta: { transition: 'slide-left' },
},
{
path: '/other-transition',
component: PanelRight,
meta: { transition: 'slide-right' },
},
]
可以根据目标路由和当前路由的关系,动态的确定使用过渡,需要利用afterEach
导航守卫,下面的例子是根据路径的深度动态添加transitionName
router.afterEach((to, from) => {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
to.meta.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
})
router.push
是一个异步方法,它会返回一个Promise,如果导航被阻止,用户停留在同一个页面上,router.push
返回的Promise
的解析值将是Navigation Failure,否则导航成功是,router.push
的返回结果是一个falsy值(通常是undefined
),这样来区分导航是否成功:
const navigationResult = await router.push('/my-profile')
if (navigationResult) {
// 导航被阻止
} else {
// 导航成功 (包括重新导航的情况)
this.isMenuOpen = false
}
Navigation Failure是一个带有额外属性的Error
实例,通过这个实例来判断哪些导航被阻止了及其原因,检查导航结果需要使用isNavigationFailure
和NavigationFailureType
import { NavigationFailureType, isNavigationFailure } from 'vue-router'
// 试图离开未保存的编辑文本界面
const failure = await router.push('/articles/2')
if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
// 给用户显示一个小通知
showToast('You have unsaved changes, discard and leave anyway?')
}
导航故障的fail
实例会暴露to
和from
属性,反应当前失败导航的当前位置和目标位置
有不同的情况会导致导航终止,可以使用isNavigationFailure
和NavigationFailureType
来区分类型:
aborted
:在导航守卫中返回false
或者调用next(false)
中断了本次导航。cancelled
: 在当前导航还没有完成之前又有了一个新的导航。比如,在等待导航守卫的过程中又调用了router.push
。duplicated
:导航被阻止,因为我们已经在目标位置了。
isNavigationFailure
接受两个参数,第一个参数是Navigation Failure实例,第二个参数是决具体的导航失败的枚举值(通过NavigationFailureType
获取),如果不传入第二个参数,则只判断当前Error是不是Navigation Failure
NavigationFailureType
包含所有可能导航失败类型的枚举值,不要使用数组,永远只使用枚举值
重定向不会阻止导航,而是创建一个新的导航,可以读取路由地址的redirectedFrom
属性,判断重定向:
await router.push('/my-profile')
if (router.currentRoute.value.redirectedFrom) {
// redirectedFrom 是解析出的路由地址,就像导航守卫中的 to和 from
}
通过addRoute
和removeRoute
来完成动态路由功能,但是这两个函数,只注册路由,如果新增路由与当前位置匹配,还需要调用router.push
或router.replace
来手动导航,才能显示该新路由
只有一个路由的配置情况下:
const router = createRouter({
history: createWebHistory(),
routes: [{ path: '/:articleName', component: Article }],
})
进入任何页面都会显示Article
组件,在组件上为/about
添加一个新路由:
router.addRoute({ path: '/about', component: About })
这时页面不会有任何改变,需要手动调用replace
方法来改变当前位置:
router.addRoute({ path: '/about', component: About })
// 我们也可以使用 this.$route 或 route = useRoute() (在 setup 中)
router.replace(router.currentRoute.value.fullPath)
如果要等待新路由的完成,可以使用await router.replace()
在导航守卫中添加或删除路由,需要通过返回新的位置来触发重定向:
router.beforeEach(to => {
if (!hasNecessaryRoute(to)) {
router.addRoute(generateRoute(to))
// 触发重定向
return to.fullPath
}
})
上面的例子实际上你是在替换要跳转的导航,在实际场景中,添加路由的欣慰更有可能是发生在导航之外,那么就不需要替换当前的导航了
router.addRoute
除了添加单个路由之外,还可以将路由的name
作为第一个参数传递,这可以添加嵌套的路由,就像通过children
添加的一样
router.addRoute({ name: 'admin', path: '/admin', component: Admin })
router.addRoute('admin', { path: 'settings', component: AdminSettings })
这等效于
router.addRoute({
name: 'admin',
path: '/admin',
component: Admin,
children: [{ path: 'settings', component: AdminSettings }],
})
当路由被删除时,所有的别名和子路由都会被删除。有几种方法来删除当前路由:
(1)添加一个名称相同的路由,那么就会删除之前的路由“
router.addRoute({ path: '/about', name: 'about', component: About })
// 这将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的
router.addRoute({ path: '/other', name: 'about', component: Other })
(2)如果是动态添加的路由,那么可以调用router.addRoute
const removeRoute = router.addRoute(routeRecord)
removeRoute() // 删除路由如果存在的话
(3)使用router.removeRoute
按名称删除路由
router.addRoute({ path: '/about', name: 'about', component: About })
// 删除路由
router.removeRoute('about')
为了避免名字冲突,可以使用Symbol
作为路由的名称
router.hasRoute()
:确认是否存在指定名称的路由,接受类型为string
或者symbol
的参数router.getRoutes
:获取所有路由记录的完整列表new Router
变为createRouter
Vue Router不再是一个类,而是一组函数:
// 以前是
// import Router from 'vue-router'
import { createRouter } from 'vue-router'
const router = createRouter({
// ...
})
mode: 'history'
被history
配置替换:
"history"
: createWebHistory()
"hash"
: createWebHashHistory()
"abstract"
: createMemoryHistory()
,用于SSR的情况import { createRouter, createWebHistory } from 'vue-router'
// 还有 createWebHashHistory 和 createMemoryHistory
createRouter({
history: createWebHistory(),
routes: [],
})
base
配置改变base
作为createWebHistory
的第一个参数传递
*
必须使用自定义的正则参数来定义全部路由
{path: '/:pathMatch(.*)', component: NotFound}
和
和
需要通过v-slot
API在
内部使用
细节还是挺多的,更详细的还是看文档吧。