vue-router
- 版本: "4.0.12" Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,是使用vue生态必不可少的一个技能
安装使用
在vue-router中一共有两个组件 和
用法:
- to
<!-- 字符串 -->
<router-link to="/home">Home</router-link>
<!-- 渲染结果 -->
<a href="/home">Home</a>
<!-- 使用 v-bind 的 JS 表达式 -->
<router-link :to="'/home'">Home</router-link>
<!-- 同上 -->
<router-link :to="{ path: '/home' }">Home</router-link>
<!-- 命名的路由 -->
<router-link :to="{ name: 'user', params: { userId: '123' }}">User</router-link>
<!-- 带查询参数,下面的结果为 `/register?plan=private` -->
<router-link :to="{ path: '/register', query: { plan: 'private' }}">
Register
</router-link>
- replace
设置 replace 属性的话,当点击时,会调用 router.replace(),而不是 router.push(),所以导航后不会留下历史记录。
<router-link to="/abc" replace></router-link>
- custom 配合 v-slot使用
是否应该将其内容包裹在 元素中。在使用 v-slot 创建自定义 RouterLink 时很有用。默认情况下, 会将其内容包裹在 元素中,即使使用 v-slot 也是如此。传递自定义的 prop,可以去除这种行为。
<router-link to="/home" custom v-slot="{ navigate, href, route }">
<a :href="href" @click="navigate">{{ route.fullPath }}</a>
</router-link>
渲染结果:
<a href="/home">/home</a>
<router-link to="/home" v-slot="{ route }">
<span>{{ route.fullPath }}</span>
</router-link>
渲染结果:
<a href="/home"><span>/home</span></a>
<!-- 这里能看到 custom 的真正作用 - 去掉a标签 但想要点击生效必须加上click事件 -->
<router-link to="/home" custom v-slot="{ route, navigate }">
<span @click="navigate">{{ route.fullPath }}</span>
</router-link>
渲染结果:
<span>/home</span>
- active-class
链接激活时,应用于渲染的 的 class。 "router-link-active" (或者全局 linkActiveClass)
- exact-active-class
- aria-current-value
当链接激活时,传递给属性 aria-current 的值。
- v-slot的使用
- href:解析后的 URL。将会作为一个 元素的 href 属性。如果什么都没提供,则它会包含 base。
- route:解析后的规范化的地址。
- navigate:触发导航的函数。 会在必要时自动阻止事件,和 - router-link 一样。例如:ctrl 或者 cmd + 点击仍然会被 navigate 忽略。
- isActive:如果需要应用 active class,则为 true。允许应用一个任意的 class。
- isExactActive:如果需要应用 exact active class,则为 true。允许应用一个任意的 class。
<router-link
to="/about"
custom
v-slot="{ href, route, navigate, isActive, isExactActive }"
>
<NavLink :active="isActive" :href="href" @click="navigate">
{{ route.fullPath }}
</NavLink>
</router-link>
- name
默认:"default"
如果 设置了 name,则会渲染对应的路由配置中 components 下的相应组件。
// 在命名视图中使用名称
<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>
// router.js
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
components: {
default: Home,
// LeftSidebar: LeftSidebar(导入的对应的vue模块页面) 的缩写
LeftSidebar,
// 它们与 `<router-view>` 上的 `name` 属性匹配
RightSidebar,
},
},
],
})
- route
一个路由地址的所有组件都已被解析(如果所有组件都被懒加载),因此可以显示。
- v-slot
主要使用 和 组件来包裹你的路由组件。
<Suspense>
<template #default>
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'" mode="out-in">
<keep-alive>
<component
:is="Component"
:key="route.meta.usePathKey ? route.path : undefined"
/>
</keep-alive>
</transition>
</router-view>
</template>
<template #fallback> Loading... </template>
</Suspense>
createRouter、createWebHistory、createWebHashHistory、createMemoryHistory、NavigationFailureType
START_LOCATION
路由所在的初始路由地址。可用于导航守卫中,以区分初始导航。
import { START_LOCATION } from 'vue-router'
router.beforeEach((to, from) => {
if (from === START_LOCATION) {
// 初始导航
}
})
Composition API
- onRoute - this.$route
- onRouter - this.$router
- onBeforeRouteLeave - 在当前位置的组件将要离开时触发
- onBeforeRouteUpdate - 在当前位置即将更新时触发
- useLink -
import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
const router = useRouter()
const route = useRoute()
function pushWithQuery(query) {
router.push({
name: 'search',
query: {
...route.query,
},
})
}
},
}
// useLink 实例
import { RouterLink, useLink } from 'vue-router'
export default {
name: 'AppLink',
props: {
// 如果使用 TypeScript,请添加 @ts-ignore
...RouterLink.props,
inactiveClass: String,
},
setup(props) {
const { route, href, isActive, isExactActive, navigate } = useLink(props)
const isExternalLink = computed(
() => typeof props.to === 'string' && props.to.startsWith('http')
)
return { isExternalLink, href, navigate, isActive }
},
}
params 参数
const routes = [
// 匹配 /o/3549
{ path: '/o/:orderId' },
// 匹配 /p/books
{ path: '/p/:productName' },
]
const routes = [
// /:orderId -> 仅匹配数字
{ path: '/:orderId(\\d+)' },
// /:productName -> 匹配其他任何内容
{ path: '/:productName' },
]
const routes = [
// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等
{ path: '/:chapters+' },
// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
{ path: '/:chapters*' },
]
如果你需要匹配具有多个部分的路由,如 /first/second/third,你应该用 *(0 个或多个)和 +(1 个或多个)将参数标记为可重复:
// 给定 { path: '/:chapters*', name: 'chapters' },
router.resolve({ name: 'chapters', params: { chapters: [] } }).href
// 产生 /
router.resolve({ name: 'chapters', params: { chapters: ['a', 'b'] } }).href
// 产生 /a/b
// 给定 { path: '/:chapters+', name: 'chapters' },
router.resolve({ name: 'chapters', params: { chapters: [] } }).href
// 抛出错误,因为 `chapters` 为空
这些也可以通过在右括号后添加它们与自定义正则结合使用:
const routes = [
// 仅匹配数字
// 匹配 /1, /1/2, 等
{ path: '/:chapters(\\d+)+' },
// 匹配 /, /1, /1/2, 等
{ path: '/:chapters(\\d+)*' },
]
你也可以通过使用 ? 修饰符(0 个或 1 个)将一个参数标记为可选:
const routes = [
// 匹配 /users 和 /users/posva
{ path: '/users/:userId?' },
// 匹配 /users 和 /users/42
{ path: '/users/:userId(\\d+)?' },
]
动态路由和动态路由匹配
动态路由 和动态路由匹配是两个概念 动态路由匹配 - 指的是通过:id 和params 接参实现的相同url带不同参数来展示不同内容 动态路由 - 在vue4.0版本中通过 addRoute来添加新的路由 removeRoute() 来删除路由
它们只注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 router.push() 或 router.replace() 来手动导航,才能显示该新路由
const router = createRouter({
history: createWebHistory(),
routes: [{ path: '/:articleName', component: Article }],
})
情况一: 在路由(导航)守卫之外使用
router.addRoute({ path: '/about', component: About })
router.addRoute({ path: '/about', component: About })
// 我们也可以使用 this.$route 或 route = useRoute() (在 setup 中)
router.replace(router.currentRoute.value.fullPath)
情况二: 在路由(导航)守卫中添加路由
router.beforeEach(to => {
if (!hasNecessaryRoute(to)) {
router.addRoute(generateRoute(to))
// 触发重定向
return to.fullPath
}
})
<!--
上面的例子有两个假设:
第一,新添加的路由记录将与 to 位置相匹配,实际上导致与我们试图访问的位置不同。
第二,hasNecessaryRoute() 在添加新的路由后返回 false,以避免无限重定向。
-->
添加嵌套路由
可以将路由的 name 作为第一个参数传递给 router.addRoute(),这将有效地添加路由,就像通过 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 }],
})
路由删除
有几个不同的方法来删除现有的路由:当路由被删除时,所有的别名和子路由也会被同时删除 当路由被删除时,所有的别名和子路由也会被同时删除
- 通过添加一个名称冲突的路由。如果添加与现有途径名称相同的途径,会先删除路由,再添加路由:
router.addRoute({ path: '/about', name: 'about', component: About })
// 这将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的
router.addRoute({ path: '/other', name: 'about', component: Other })
- 通过调用 router.addRoute() 返回的回调
const removeRoute = router.addRoute(routeRecord)
removeRoute() // 删除路由如果存在的话 当路由没有名称时,这很有用。
- 通过使用 router.removeRoute() 按名称删除路由:
router.addRoute({ path: '/about', name: 'about', component: About })
// 删除路由
router.removeRoute('about')
查看功能方法
- router.hasRoute():检查路由是否存在。
- router.getRoutes():获取一个包含所有路由记录的数组。 相当于直接返回了 this.$router.options.routes
路由守卫
- beforeEach()
const router = createRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
// 返回 false 以取消导航
return false
})
- beforeResolve()
和 router.beforeEach 类似,因为它在 每次导航时都会触发,但是确保在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用。这里有一个例子,确保用户可以访问自定义 meta 属性
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... 处理错误,然后取消导航
return false
} else {
// 意料之外的错误,取消导航并把错误传给全局处理器
throw error
}
}
}
})
- afterEach
和前置守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身: 它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。
router.afterEach((to, from, failure) => {
if (!failure) sendToAnalytics(to.fullPath)
})
- 路由独享
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
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],
},
{
path: '/about',
component: UserDetails,
beforeEnter: [removeQueryParams],
},
]
- 组件内的守卫
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
- 可用的配置 API
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫(2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
核心实现原理及简单实现
在配置使用的时候主要分以下几步:
- 一、使用router插件
import Router from 'vue-router'
Vue.use(Router)
- 二、创建Router实例, router.js
export default new Router({...})
- 三、在跟组件上添加实例 main.js
import router from './router'
new Vue({
router,
}).$mount("#app")
- 四: 添加路由视图
<router-view></router-view>
在vue的插件实现中会调用vue.use(Router) use的调用会 router插件中的install方法;
以下代码仅为简单的说明其核心实现的原理
- 1,通过对url的监听 并记录当前要前往的路由current
- 2,通过current和用户配置的router里的pathd对比活的其对应的component 并渲染到router-view中
// 将Vue单独保存出来方便使用
let _Vue;
// 定义了VueRouter类 new
class VueRouter{
construcor(options){
this.options = options;
}
}
// 实现install 会在vue.use的时候调用 在源码中有独立的文件定义install
VueRouter.install = function(Vue){
_Vue = Vue
// 由于在use的时候 没有实例化所以是没有options 所以需要借用混入的生命周期来延迟以获options
Vue.mixin({
beforeCreate(){
if (this.$options.router){
Vue.prototype.$router = this.$options.router
//使用vue提供的 defineReactive 将保存下来的路由参 设置为响应式的数据
window.addEventListener('hashchange', this.onHashChange.bind(this))
}
}
})
// 实现vue-router的组件
Vue.component('router-link', Link)
vue.component('router-view', view)
}
//创建 router-link
export default {
props:{
to:String,
required: true
}
render(h){
return h('a', {
attrs:{
href: '#' + this.to
}
})
}
}
//创建 router-view
export default {
render(h){
const { $options, current } = this.$router
let component;
$options.routes.map(n => {
if (n.path == current){
component = n.component
}
})
return h(component)
}
}
参考: