Vue-Router学习笔记,持续记录
- Vue学习
- 2022-01-13
- 1151热度
- 0评论
前端路由
1. hash 模式
随着 ajax 的流行,异步数据请求交互运行在不刷新浏览器的情况下进行。而异步交互体验的更高级版本就是 SPA —— 单页应用。单页应用不仅仅是在页面交互是无刷新的,连页面跳转都是无刷新的,为了实现单页应用,所以就有了前端路由。
类似于服务端路由,前端路由实现起来其实也很简单,就是匹配不同的 url 路径,进行解析,然后动态的渲染出区域 html 内容。但是这样存在一个问题,就是 url 每次变化的时候,都会造成页面的刷新。那解决问题的思路便是在改变 url 的情况下,保证页面的不刷新。在 2014 年之前,大家是通过 hash 来实现路由,url hash 就是类似于:
http://www.xxx.com/#/login
这种 #。后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。另外每次 hash 值的变化,还会触发hashchange 这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。然后我们便可以监听hashchange来实现更新页面部分内容的操作:
function matchAndUpdate () {
/* todo 匹配 hash 做 dom 更新操作*/
}
window.addEventListener('hashchange', matchAndUpdate)
2. history 模式
14年后,因为 HTML5 标准发布。多了两个 API,pushState 和 replaceState,通过这两个 API 可以改变 url 地址且不会发送请求。同时还有popstate 事件。通过这些就能用另一种方式来实现前端路由了,但原理都是跟 hash 实现相同的。用了 HTML5 的实现,单页路由的 url 就不会多出一个#,变得更加美观。但因为没有 # 号,所以当用户刷新页面之类的操作时,浏览器还是会给服务器发送请求。为了避免出现这种情况,所以这个实现需要服务器的支持,需要把所有路由都重定向到根页面。
function matchAndUpdate () {
/* todo 匹配路径 做 dom 更新操作 */
}
window.addEventListener('popstate', matchAndUpdate)
资料来源:https://zhuanlan.zhihu.com/p/37730038
3. 区别
- url 展示上,hash 模式有“#”,history 模式没有
- 刷新页面时,hash 模式可以正常加载到 hash 值对应的页面,而 history 没有处理的话,会返回 404,一般需要后端将所有页面都配置重定向到首页路由
- 兼容性,hash 可以支持低版本浏览器和 IE。
Vue Router
官方文档:https://router.vuejs.org/zh/api/、https://router.vuejs.org/、https://nicen.cn/vue.html(Vue笔记)
说明:普通路由、动态路由、嵌套路由、命名路由、命名视图
1.Vue2.x中使用
import Router from 'vue-router' /*引入Vuerouter*/
Vue.use(Router) /*安装插件*/
/* 基本的路由配置 */
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
/* 创建 router 实例,然后传 `routes` 配置 */
const router = new VueRouter({
routes: routes
})
/*创建和挂载根实例,通过 router 配置参数注入路由,让整个应用都有路由功能 */
const app = new Vue({
router
}).$mount('#app')
2.Vue3.x中使用
import {createRouter,createWebHashHistory} from 'vue-router'
import {createApp} from 'vue'
/* 创建 router 实例,然后传 `routes` 配置 */
const router = createRouter({
history: createWebHashHistory(),
routes: routes
})
/* 创建并挂载根实例 */
const app = createApp({})
/* 确保 _use_ 路由实例使,整个应用支持路由 */
app.use(router)
app.mount('#app')
基础知识
Vue+Vue Router主要用于单页面应用创建;vue-router.js
会暴露一个VueRouter构造方法,通过传入一个路由规则配置对象创建路由器(Router);
//Vue2.x
const router = new VueRouter({
routes: routes
})
//Vue3.x
const router = createRouter({
history: createWebHashHistory(),
routes: routes
})
router-link
,用于显示指定路由的组件。 router-view
用于显示路由的组件。
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
1.路由下的组件
- .组件可以分为一般组件(直接通过标签使用)和路由组件(通过路由显示),通常可以将两类组件分开存放,便于管理。(components/pages)。路由组件比普通组件会多$route(当前组件相关的路由信息)和$router(指向定义的整个路由器)属性;
- 通过注入路由器,我们可以在任何组件内通过
this.$router
访问路由器,也可以通过this.$route
访问当前路由 - 路由过程中被隐藏的组件会被销毁(keep-alive下的组件将会被保留);
2.嵌套路由(多级路由)
router-view标签内,显示的组件同样可以包含router-view标签和使用路由router-link;同样的也是在VueRouter
的路由规则中需使用 children 配置;多级路由下,router-link的to属性需要使用完整的组件路径。
const router = new VueRouter({
routes: [
{
path: '/user',
component: User,
children: [
{
path: 'children',
component: UserHome
}
/*其他子路由*/
]
}
]
})
当路由未匹配到指定的组件时,{path:''}指定的组件将作为默认显示。
3.命名路由,重定向路由、别名
除了 path 之外,你还可以为任何路由提供 name。
const routes = [
{
path: '/user/:username',
name: 'user',
component: User
}
]
router.push({ name: 'user', params: { username: 'erina' } })
还可以提供redirect用于重定向(重定向是指当用户访问 /home 时,URL 会被 / 替换,然后匹配成 /的路由。),在写 redirect 的时候,可以省略 component 配置,因为它从来没有被直接访问过,所以没有组件要渲染。
//可以是回调函数,可以是命名路由的name,也可以是path,也可以是路由对象,也可以是代表path的字符串(不使用/代表相对地址)
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
还可以通过alias定义别名访问(将 / 别名为 /home,意味着当用户访问 /home 时,URL 仍然是 /home,但会被匹配为用户正在访问 /。),与重定向不同的是别名的URL不会被替换(访问/home实际访问的是/);
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
4.命名视图
命名视图用于同时展现多个路由视图,可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。
<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>
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s):
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `<router-view>` 上的 `name` 属性匹配
RightSidebar,
},
},
],
})
5. 导航守卫
资料:https://zhuanlan.zhihu.com/p/54112006
“导航”表示路由正在发生改变。导航守卫是路由跳转过程中的一些钩子函数,路由跳转是一个大的过程,这个大的过程分为跳转前中后等等细小的过程,在每一个过程中都有一函数,这个函数能让你操作一些其他的事儿的时机,这就是导航守卫。
导航守卫分为:全局的、单个路由独享的、组件内的三种。
a.全局导航守卫
- 指路由实例上直接操作的钩子函数,他的特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数
- 全局前置守卫(
beforeEach
):任意一个导航触发时,都会触发这个钩子函数。const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { .... })
to: Route
即将要进入的目标 路由对象、from: Route
当前导航正要离开的路由 对象
next: Function
resolve 进入下一个钩子函数。执行效果依赖 next 方法的调用参数。
next():
进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
next(false):
中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
next('/')
或者next({ path: '/' }):
跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
next(error):
(2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。 - 全局解析守卫(
beforeResolve
),和beforeEach区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用,参数也是to,from,next三个。即在 beforeEach 和 组件内beforeRouteEnter 之后,afterEach之前调用。 - 全局后置钩子(
afterEach
):和beforeEach相反,他是在路由跳转完成后触发,参数包括to,from没有了next(参数会单独介绍),他发生在beforeEach和beforeResolve之后,beforeRouteEnter(组件内守卫,后讲)之前。
b.路由独享的守卫
- 【路由独享的】是指在单个路由配置的时候也可以设置的钩子函数,其位置就是下面示例中的位置,也就是像Foo这样的组件都存在这样的钩子函数。目前他只有一个钩子函数beforeEnter。
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { /*...*/ } } ] })
和beforeEach完全相同,如果都设置则在beforeEach之后紧随执行,参数to、from、next
c.组件内的守卫
可以在路由组件内直接定义以下路由导航守卫:
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
/* 在渲染该组件的对应路由被 confirm 前调用*/
/*不!能!获取组件实例 `this`*/
/*因为当守卫执行前,组件实例还没被创建*/
},
beforeRouteUpdate(to, from, next) {
/* 在当前路由改变,但是该组件被复用时调用*/
/*举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,*/
/*由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。*/
/*可以访问组件实例 `this`*/
},
beforeRouteLeave(to, from, next) {
/*导航离开该组件的对应路由时调用*/
/* 可以访问组件实例 `this`*/
}
}
6.完整的路由导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
7.组件内的守卫
beforeRouteEnter
路由进入之前调用,参数包括to,from,next。该钩子在全局守卫beforeEach和独享守卫beforeEnter之后,全局beforeResolve和全局afterEach之前调用,要注意的是该守卫内访问不到组件的实例,也就是this为undefined,也就是他在beforeCreate生命周期前触发。在这个钩子函数中,可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数,可以在这个守卫中请求服务端获取数据,当成功获取并能进入路由时,调用next并在回调中通过 vm访问组件实例进行赋值等操作,(next中函数的调用在mounted之后:为了确保能对组件实例的完整访问)。
beforeRouteEnter (to, from, next) {
/*这里还无法访问到组件实例,this === undefined*/
next( vm => {
/*通过 `vm` 访问组件实例*/
})
}
8.路由切换动画效果
router-view支持使用transition效果;
//vue3.x
<router-view v-slot="{ Component }">
<transition name="fade">
<component :is="Component" />
</transition>
</router-view>
//vue2.x
<transition>
<router-view></router-view>
</transition>
匹配所有路由,匹配404请求
1.vue2.x
vue2.x下的router可以直接使用*通配符匹配所有路由,当没有任何一个路由项被匹配时将由*路由进行处理。
2.vue3.x
const routes = [
// 将匹配所有内容并将其放在 `$route.params.pathMatch` 下
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }
]
pathMatch代表的是参数名,()内代表参数需要匹配的正则,(.*)代表匹配任意参数,也就相当于任意路由。
路由元信息
定义路由的时候可以配置 meta
字段(元,如其他理念一般,用于描述这个路由记录的一些信息)
我们称呼 routes 配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录,一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched
数组。因此,我们需要遍历 $route.matched
来检查路由记录中的 meta 字段。
https://router.vuejs.org/zh/guide/advanced/meta.html
路由配置对象
Vue-router API:https://router.vuejs.org/zh/api/
- path,记录的路径。应该以
/
开头,除非该记录是另一条记录的子记录。可以定义参数:/users/:id
匹配/users/1
以及/users/posva
。 - redirect,如果路由是直接匹配的,那么重定向到哪里呢。重定向发生在所有导航守卫之前,并以新的目标位置触发一个新的导航。也可以是一个接收目标路由地址并返回我们应该重定向到的位置的函数。匹配了就直接重定向
- children,路由的嵌套路由
- alias,路由的别名。允许定义类似记录副本的额外路由。这使得路由可以简写为像这种
/users/:id
和/u/:id
。 所有的alias
和path
值必须共享相同的参数。 - name,路由记录独一无二的名称。
- beforeEnter,在进入特定于此记录的守卫之前。注意如果记录有
重定向
属性,则beforeEnter
无效。 - props,允许将参数作为 props 传递给由
router-view
渲染的组件。当传递给一个多视图记录时,它应该是一个与组件
具有相同键的对象,或者是一个应用于每个组件的布尔值
。 - meta,在记录上附加自定义数据。
路由懒加载、异步组件
Vue Router 支持开箱即用的动态导入,懒加载:使用到的时候才加载。(通过Vue的异步组件和Webpack的动态引入来实现)
const UserDetails = () => import('./views/UserDetails')
component:UserDetails
//或者
component:()=>import("/pages/rand.vue")
路由中不需要使用异步组件,因为路由本身就支持动态引入,组件跟普通组件一样的定义即可;
let router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes : [
{
path: "/", component: () => import("./pages/index.vue")
}
]
});
编程式导航
官方文档:https://v3.router.vuejs.org/zh/guide/essentials/navigation.html#%E6%93%8D%E4%BD%9C-history
除了使用 <router-link>
创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。(参考小程序的导航方法)
在 Vue 实例内部,你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push。
- router.push(location, onComplete?, onAbort?),导航到一个新地址,产生新的history。
- router.replace(location, onComplete?, onAbort?),跟
router.push
很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。 - router.go(n),这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步。
路由参数
当前页面的路由对象(route)参数相关的API:
- $router.query,从 URL 的 search 部分提取的已解码查询参数的字典。
- $router.params,从 path 中提取的已解码参数字典
- $router.hash,已解码 URL 的 hash 部分。总是以 #开头。如果 URL 中没有 hash,则为空字符串。
1.布尔模式
参数路由:后的参数可以使用正则表达式,例如/:user?,代表可选参数,以下代表可重复的参数
const routes = [
// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等
{ path: '/:chapters+' },
// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
{ path: '/:chapters*' },
]
这将提供一个参数数组,而不是一个字符串,并且在使用命名路由时也需要你传递一个数组。
官方文档:https://router.vuejs.org/zh/guide/essentials/passing-props.html
# id将作为路由组件的props传入
const routes = [
{
path: '/user/:id',
props: true
}
]
2.对象模式
const routes = [
{
path: '/promotion/from-newsletter',
component: Promotion,
props: { newsletterPopup: false }
}
]
对象将作为路由组件的props(名和值相对应)传入。
3.函数模式
const routes = [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
路由自己的props和query相关联。
路由动态增删
动态路由主要通过两个函数实现。router.addRoute() 和 router.removeRoute()。它们只注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 router.push() 或 router.replace() 来手动导航,才能显示该新路由。
1.addRoute
添加一条新的路由记录到路由。如果路由有一个 name,并且已经有一个与之名字相同的路由,它会先删除之前的路由。
2.removeRoute
通过名称删除现有路由。
Vue-router4.x
在setup获取当前路由和路由器对象
- 1.useRoute,返回当前路由地址。相当于在模板中使用 $route。必须在 setup() 中调用。
- 2.useRouter,返回 router 实例。相当于在模板中使用 $router。必须在 setup() 中调用。
- 3.路由中不需要使用Vue3.x中的异步组件,因为路由本身就支持动态引入,组件跟普通组件一样的定义即可;
- 4. useRouter执行一定要放在setup方法内的顶部或者其他位置,不能放在下面setup的函数里面执行,否则作用域改变useRouter执行是undefined
- 5. vue-router在网页打开运行js的时候就开始接管路由了,然后会根据当前访问的链接去匹配 对应的路由(如:链接edit,匹配path为edit的路由并加载),如果没有匹配就404。(匹配的同时会跟卤router的配置改变URl,如history模式下的baseurl)。
VueRouter从Vue2.x到3.x
- new Router 变成 createRouter,Vue Router 不再是一个类,而是一组函数。
- 新的 history 配置取代 mode:
"history"=> createWebHistory()
"hash"=>createWebHashHistory()
"abstract"=> createMemoryHistory() - base 配置被作为 createWebHistory (其他 history 也一样)的第一个参数传递
网络数据获取
有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
1.导航完成之后获取
先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
2.导航完成之前获取
导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
导航故障
1.情形
- 用户已经位于他们正在尝试导航到的页面
- 一个导航守卫通过调用 return false 中断了这次导航
- 当前的导航守卫还没有完成时,一个新的导航守卫会出现了
- 一个导航守卫通过返回一个新的位置,重定向到其他地方 (例如,return '/login')
- 一个导航守卫抛出了一个 Error
路由守卫内可以返回一个Promise对象,调用函数导航方法将返回一个对象,通过这个对象可以判断导航成功与否。
导航是异步的
导航完成了,才会调用如下方法:
await router.push('/my-profile')
this.isMenuOpen = false
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。
实践问题总结
1. 单页面应用下,用户点击刷新
history模式下,路由变化会改变当前的URL,正常的浏览器环境下,用户刷新时,请求的链接仍然是最开始请求的入口链接。但是在企业微信的浏览器内,则是用改变后的链接去刷新。改为Hash模式保持链接不变,可避免出现404;
2.使用Vue-router之后的运行流程
use Vue-router —> 进入App.vue —> 加载初始化的Url(通过当前访问的URL匹配 = 创建路由对象的时的基址+路由路径) —> 路由路径匹配成功 —> 加载路由对应的组件 —> 渲染到App.vue的router-view标签 —> 加载完毕
3.普通js文件中使用vue-router对象
直接引入创建好的router对象,然后就可以和正常的router对象一样使用了。setup中需要使用useRouter方法,并且必须是在代码块的开头。
4.“/”的子路由
“/”的子路由实际就等同于一级路由一样的效果,可以直接通过 “/route”匹配后访问。
5.和keep-alive一起使用
//vue2.x
<keep-alive>
<router-view></router-view>
</keep-alive>
//Vue3.x
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
如果报错:parentComponent.ctx.deactivate is not a function,保证key为唯一标识,可解决问题:
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" :key="$route.name">
</keep-alive>
</router-view>
6. 懒加载和非懒加载的使用区别
不使用懒加载,组件在路由对象初始化的时候就会加载,加载所有引入的依赖、文件等等,有些时候组件可能引用了一些外部js文件,这些文件在组件随着路由加载的时候就会运行。例如h5plus的plus 对象,原本设置的是在App.vue内监听加载事件初始化之后才能调用的全局对象,但是由于路由不是懒加载,组件内调用的外部js提前运行并调用了plus对象,导致js发生致命错误。
懒加载的资源消耗极少,在使用过程中基本感受不到区别,所以路由尽量都使用懒加载。
7.记录一次vue-router不渲染组件
全局路由守卫调用的函数内,没有调用next,导致整个逻辑被挂起,不渲染组件。
8.路由匹配的优先级
经过测试,先定义的路由优先级低于后定义的路由。测试用例如下:
{
path:"/",
name:"登录",
component:()=>import("../pages/login")
},
{
path:"/",
name:"首页",
component:()=>import("../pages/index")
}
9.父路由不会自动重定向子路由
访问父路由不会默认访问第一个子路由,必须指定子路由才会访问。
10.router-view 的 v-slot
<router-view>
暴露了一个 v-slot API,主要使用 <transition>
和 <keep-alive>
组件来包裹你的路由组件。
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'" mode="out-in">
<keep-alive>
<suspense>
<template #default>
<component
:is="Component"
:key="route.meta.usePathKey ? route.path : undefined"
/>
</template>
<template #fallback> Loading... </template>
</suspense>
</keep-alive>
</transition>
</router-view>
- Component: 要传递给 的 VNodes 是 prop。
- route: 解析出的标准化路由地址。
11.子路由路径改成根路径
子路由的path可以是 "/child"这种一级路径,加载子路由同时也会加载所有父级路由组件
12. 路由定义的规则
- 同层级的路由name和path不能相同,相同的有一个会匹配不到;
- 不同层级的name不能相同、path可以相同,相同的有一个会匹配不到;
- 子路由路径可以是相对路径也可以是绝对路径;
- redirect重定向的路径可以是绝对路径也可以是相对路径;
- 父路由可以不绑定组件;子路由会直接显示到上层组件;
13.子组件的router-view
子组件内写的router-view可以作为父路由组件的渲染区域。也就是假设A是路由a的访问的组件,A内有一个子组件内有router-view组件,a路由下面还有子路由;访问a的子路由时,会渲染到A的子组件的router-view
14.如何让父组件不渲染?
vue-router,如果直接redirect到子孙组件,中间的父组件可以不指定component;也可以通过指定一个只包含router-view的组件,来让父组件不渲染额外的组件;
component: {render: () => h(RouterView)},
15.参数路由参数变化时页面不更新
参数路由在参数变化进行切换的时候,由于页面的路由是一样的,只是最后一个参数不同,不会触发页面的数据请求。
解决办法:router-view页面添加key,将fullPath作为每个页面的唯一值,当key值不同时,页面就会刷新
16.next函数
next(to),取消后面的所有导航守卫,重定向到新的路由。
17.parseQuery, stringifyQueryparseQuery
序列化和反序列化router query查询参数