快来跟前端人一起交流学习吧,致力营造成资源共享型社区。
目录
数据获取 数据获取 | Vue Router (vuejs.org)
前言
这是我在实际工作中用到的先写这:
/**
-
Note: 路由配置项
-
hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
-
alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式–如组件页面
-
// 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
-
// 若你想不管路由下面的 children 声明的个数都显示你的根路由
-
// 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
-
redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
-
name:‘router-name’ // 设定路由的名字,一定要填写不然使用时会出现各种问题
-
meta : {
roles: [‘admin’,‘editor’] // 设置该路由进入的权限,支持多个权限叠加
title: ‘title’ // 设置该路由在侧边栏和面包屑中展示的名字
icon: ‘svg-name’ // 设置该路由的图标,对应路径src/icons/svg
breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示
}
*/
使用
–
通过调用 app.use(router),我们可以在任意组件中以 this. r o u t e r 的形式访问它,并且以 t h i s . router 的形式访问它,并且以 this. router 的形式访问它,并且以 this.route 的形式访问当前路由:
// 5. 创建并挂载根实例
const app = Vue.createApp({})
//确保 use 路由实例使
//整个应用支持路由。
app.use(router)
app.mount(‘#app’)
要在 setup 函数中访问路由,请调用 useRouter 或 useRoute 函数
/users/johnny 和 /users/jolyne 这样的 URL 都会映射到同一个路由。
// 这些都会传递给 createRouter
const routes = [
// 动态段以冒号开始
{ path: ‘/users/:id’, component: User },
]
路径参数 用冒号 : 表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params 的形式暴露出来。因此,我们可以通过更新 User 的模板来呈现当前的用户 ID:
用户从 /users/johnny 导航到 /users/jolyne 时,相同的组件实例将被重复使用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会被调用。
捕获所有路由或 404 Not found 路由
想匹配任意路径,我们可以使用自定义的 路径参数 正则表达式,在 路径参数 后面的括号中加入 正则表达式 :
const routes = [
// 将匹配所有内容并将其放在 $route.params.pathMatch
下
{ path: ‘/:pathMatch(.)’, name: ‘NotFound’, component: NotFound },
// 将匹配以 /user-
开头的所有内容,并将其放在 $route.params.afterUser
下
{ path: ‘/user-:afterUser(.*)’, component: UserGeneric },
]
当你访问 /user/eduardo 时,在 User 的 router-view 里面什么都不会呈现,因为没有匹配到嵌套路由。也许你确实想在那里渲染一些东西。在这种情况下,你可以提供一个空的嵌套路径:
const routes = [
{
path: ‘/user/:id’,
component: User,
children: [
// when /user/:id is matched
// 当 /user/:id 匹配成功
// UserHome 将被直接渲染到 User 的 内部
{ path: ‘’, component: UserHome },
// …其他子路由
],
},
]
编程式导航
点击 时,内部会调用这个方法,所以点击 相当于调用 router.push(…) :
声明式 | 编程式 |
<router-link :to="..."> | router.push(...) |
替换当前位置
它的作用类似于 router.push,唯一不同的是,它在导航时不会向 history 添加新记录,正如它的名字所暗示的那样——它取代了当前的条目。
声明式 | 编程式 |
<router-link :to="..." replace> | router.replace(...) |
该方法采用一个整数作为参数,表示在历史堆栈中前进或后退多少步,类似于 window.history.go(n)。
// 向前移动一条记录,与 router.forward() 相同
router.go(1)
// 返回一条记录,与router.back() 相同
router.go(-1)
// 前进 3 条记录
router.go(3)
// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)
router.push、router.replace 和 router.go 是 window.history.pushState、window.history.replaceState 和 window.history.go 的翻版,它们确实模仿了 window.history 的 API。
值得一提的是,无论在创建路由器实例时传递什么样的history 配置,Vue Router 的导航方法(push、replace、go)都能始终如一地工作。
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。
别名
通过别名,你可以自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制。
布尔模式
当 props 设置为 true 时,route.params 将被设置为组件的 props。
const User = {
template: ‘
}
const routes = [{ path: ‘/user/:id’, component: User }]
//改为
const User = {
props: [‘id’],
template: ‘
}
const routes = [{ path: ‘/user/:id’, component: User, props: true }]
不同的历史记录模式
history 配置允许我们在不同的历史模式中进行选择。
hash 模式
import { createRouter, createWebHashHistory } from ‘vue-router’
const router = createRouter({
history: createWebHashHistory(),
routes: [
//…
],
})
它在内部传递的实际 URL 之前使用了一个哈希字符(#)。它在 SEO 中确实有不好的影响
HTML5 模式
用 createWebHistory() 创建 HTML5 模式,推荐使用这个模式:
不带#号,
不过,问题来了。由于我们的应用是一个单页的客户端应用,如果没有适当的服务器配置,用户在浏览器中直接访问 https://example.com/user/id,就会得到一个 404 错误。这就丑了。
要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面。漂亮依旧!
不同的历史模式 | Vue Router (vuejs.org)")
导航守卫
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。
全局前置守卫
router.beforeEach 注册一个全局前置守卫:
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。
每个守卫方法接收两个参数:
-
to: 即将要进入的目标
-
from: 当前导航正要离开的路由
可以返回return的值如下:
-
false: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
-
一个路由地址: 通过一个路由地址跳转到一个不同的地址,就像你调用 router.push()") 一样,你可以设置诸如 replace: true 或 name: ‘home’ 之类的配置。当前的导航被中断,然后进行一个新的导航,就和 from 一样。
如果遇到了意料之外的情况,可能会抛出一个 Error。这会取消导航并且调
用 router.onError()") 注册过的回调。
如果什么都没有,undefined 或返回 true,则导航是有效的,并调用下一个导航守卫
全局解析守卫
你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,因为它在 每次导航时都会触发,但是确保在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用。
router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。
全局后置钩子
router.afterEach
和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:
它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。
路由独享的守卫
可以直接在路由配置上定义 beforeEnter 守卫:
beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。
const routes = [
{
path: ‘/users/:id’,
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
你也可以将一个函数数组传递给 beforeEnter,这在为不同的路由重用守卫时很有用:
组件内的守卫
你可以在路由组件内直接定义路由导航守卫(传递给路由配置的)
可用的配置 API
你可以为路由组件添加以下配置:
-
beforeRouteEnter
-
beforeRouteUpdate
-
beforeRouteLeave
const UserDetails = {
template: ...
,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 this
!
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /users/:id
,在 /users/1
和 /users/2
之间跳转的时候,
// 由于会渲染同样的 UserDetails
组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 this
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 beforeRouteUpdate
一样,它可以访问组件实例 this
},
}
beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数:
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 vm
访问组件实例
})
}
这个 离开守卫 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false 来取消。
beforeRouteLeave (to, from) {
const answer = window.confirm(‘Do you really want to leave? you have unsaved changes!’)
if (!answer) return false
}
完整的导航解析流程
-
导航被触发。
-
在失活的组件里调用 beforeRouteLeave 守卫。
-
调用全局的 beforeEach 守卫。
-
在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
-
在路由配置里调用 beforeEnter。
-
解析异步路由组件。
-
在被激活的组件里调用 beforeRouteEnter。
-
调用全局的 beforeResolve 守卫(2.5+)。
-
导航被确认。
-
调用全局的 afterEach 钩子。
-
触发 DOM 更新。
-
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
路由元信息
有时,你可能希望将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的meta属性来实现,并且它可以在路由地址和导航守卫上都被访问到。
const routes = [
{
path: ‘/posts’,
component: PostsLayout,
children: [
{
path: ‘new’,
component: PostsNew,
// 只有经过身份验证的用户才能创建帖子
meta: { requiresAuth: true }
},
{
path: ‘:id’,
component: PostsDetail
// 任何人都可以阅读文章
meta: { requiresAuth: false }
}
]
}
]
我们称呼 routes 配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,它可能匹配多个路由记录。
一个路由匹配到的所有路由记录会暴露为 r o u t e 对象 ( 还有在导航守卫中的路由对象 ) 的 route 对象(还有在导航守卫中的路由对象)的 route 对象(还有在导航守卫中的路由对象)的route.matched 数组。
我们需要遍历这个数组来检查路由记录中的 meta 字段,但是 Vue Router 还为你提供了一个 $route.meta 方法,它是一个非递归合并所有 meta 字段的(从父字段到子字段)的方法。这意味着你可以简单地写
router.beforeEach((to, from) => {
// 而不是去检查每条路由记录
// to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !auth.isLoggedIn()) {
// 此路由需要授权,请检查是否已登录
// 如果没有,则重定向到登录页面
return {
path: ‘/login’,
// 保存我们所在的位置,以便以后再来
query: { redirect: to.fullPath },
}
}
})
TypeScript
可以通过扩展 RouteMeta 接口来输入 meta 字段:
// typings.d.ts or router.ts
import ‘vue-router’
declare module ‘vue-router’ {
interface RouteMeta {
// 是可选的
isAdmin?: boolean
// 每个路由都必须声明
requiresAuth: boolean
}
}
数据获取 数据获取 | Vue Router (vuejs.org)")
有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
-
导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
-
导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
从技术角度讲,两种方式都不错 —— 就看你想要的用户体验是哪种。
导航完成后获取数据
当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的 created 钩子中获取数据。这让我们有机会在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。
在导航完成前获取数据
通过这种方式,我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 beforeRouteEnter 守卫中获取数据,当数据获取成功后只调用 next 方法:
在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示。如果数据获取失败,同样有必要展示一些全局的错误提醒。
Vue Router 和 组合式 API
在 setup 中访问路由和当前路由
route 对象是一个响应式对象,所以它的任何属性都可以被监听,但你应该避免监听整个 route 对象:
因为我们在 setup 里面没有访问 this,所以我们不能再直接访问 this. r o u t e r 或 t h i s . router 或 this. router 或 this.route。作为替代,我们使用 useRouter 函数:
请注意,在模板中我们仍然可以访问 r o u t e r 和 router 和 router 和 route,所以不需要在 setup 中返回 router 或 route。
导航守卫
虽然你仍然可以通过 setup 函数来使用组件内的导航守卫,但 Vue Router 将更新和离开守卫作为 组合式 API 函数公开:
import { onBeforeRouteLeave, onBeforeRouteUpdate } from ‘vue-router’
export default {
setup() {
// 与 beforeRouteLeave 相同,无法访问 this
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
‘Do you really want to leave? you have unsaved changes!’
)
// 取消导航并停留在同一页面上
if (!answer) return false
})
const userData = ref()
// 与 beforeRouteLeave 相同,无法访问 this
onBeforeRouteUpdate(async (to, from) => {
//仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
},
}
组合式 API 守卫也可以用在任何由 渲染的组件中,它们不必像组件内守卫那样直接用在路由组件上。
useLink
Vue Router 将 RouterLink 的内部行为作为一个组合式 API 函数公开。它提供了与 v-slot API 相同的访问属性:
过渡动效
想要在你的路径组件上使用转场,并对导航进行动画处理,你需要使用 v-slot API:
对所有的路由使用相同的过渡
//把原来这个路由视图换成这个加动画的试图即可
单个路由的过渡
想让每个路由的组件有不同的过渡,你可以将元信息和动态的 name 结合在一起,放在 上:
const routes = [
{
path: ‘/custom-transition’,
component: PanelLeft,
meta: { transition: ‘slide-left’ },
},
{
path: ‘/other-transition’,
component: PanelRight,
meta: { transition: ‘slide-right’ },
},
]
基于路由的动态过渡
也可以根据目标路由和当前路由之间的关系,动态地确定使用的过渡。使用和刚才非常相似的片段:
我们可以添加一个 after navigation hook (后置钩子),根据路径的深度动态添加信息到 meta 字段。
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’
})
滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
注意: 这个功能只在支持 history.pushState 的浏览器中可用。
当创建一个 Router 实例,你可以提供一个 scrollBehavior 方法:
const router = createRouter({
history: createWebHashHistory(),
routes: […],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
}
})
scrollBehavior 函数接收 to和 from 路由对象,如 Navigation Guards。第三个参数 savedPosition,只有当这是一个 popstate 导航时才可用(由浏览器的后退/前进按钮触发)。
该函数可以返回一个 ScrollToOptions 位置对象:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
// 始终滚动到顶部
return { top: 0 }
},
})
你也可以通过 el 传递一个 CSS 选择器或一个 DOM 元素。在这种情况下,top 和 left 将被视为该元素的相对偏移量。
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
// 始终在元素 #main 上方滚动 10px
return {
// 也可以这么写
// el: document.getElementById(‘main’),
el: ‘#main’,
top: -10,
}
},
})
如果返回一个 falsy 的值,或者是一个空对象,那么不会发生滚动。
返回 savedPosition,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
},
})
如果你要模拟 “滚动到锚点” 的行为:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
}
}
},
})
如果你的浏览器支持滚动行为,你可以让它变得更流畅:
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
behavior: ‘smooth’,
}
}
}
})
延迟滚动
当处理过渡时,我们希望等待过渡结束后再滚动。要做到这一点,你可以返回一个 Promise,它可以返回所需的位置描述符。
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ left: 0, top: 0 })
}, 500)
})
},
})
我们可以将其与页面级过渡组件的事件挂钩,以使滚动行为与你的页面过渡很好地结合起来
路由懒加载
/ 将
// import UserDetails from ‘./views/UserDetails’
// 替换成
const UserDetails = () => import(‘./views/UserDetails’)
const router = createRouter({
// …
routes: [{ path: ‘/users/:id’, component: UserDetails }],
})
component (和 components) 配置接收一个返回 Promise 组件的函数,Vue Router 只会在第一次进入页面时才会获取这个函数,然后使用缓存数据。这意味着你也可以使用更复杂的函数,只要它们返回一个 Promise :
const UserDetails = () =>
Promise.resolve({
/* 组件定义 */
})
const router = createRouter({
routes: [{ path: ‘/users/:id’, component: UserDetails }],
})
一般来说,对所有的路由都使用动态导入是个好主意。