详解Vue Router的要点知识
路由
1.router-link
<router-link to="/路由路径/参数" tag="button">xxx</router-link>
:用于跳转路由;
to
代表跳转的路由;
tag
代表router-link被渲染成什么标签;默认会被渲染成一个a标签;<router-view></router-view>
:用于显示组件;
当组件进行切换时,会将组件销毁;
创建路由四步
<body>
<div id="app">
<router-link to="/home" tag="button">首页</router-link>
<router-link to="/login" tag="button">登录</router-link>
<router-view></router-view>
</div>
<script src="node_modules/vue/dist/vue.js"></script>
<script src="node_modules/vue-router/dist/vue-router.js"></script>
<script>
//1.创建(路由)组件;
let home = {
data(){
return{con:'首页'}
},
template:'<div>{{con}}</div>'
}
let login = {
data(){
return{logo:'登录'}
},
template:'<div>{{logo}}</div>'
}
//2.配置路由映射表:根据路由不同,显示不同的组件;
let routes = [
{ path: '/home', component: home },
{ path: '/login', component: login }
]
//3.注册映射表:创建 router 实例,然后传 `routes` 配置;
let router = new VueRouter({
//routes:routes //名字相同可以写一个,简化
routes
})
//4.路由挂载到根实例
let vm = new Vue({
el:'#app',
router
})
</script>
</body>
通过注入路由器,我们可以在任何组件内通过 this.$router
访问路由器,也可以通过 this.$route
访问当前路由:
let list = {
methods:{
goBack(){
this.$router.go(-1);
}
},
computed:{
username () {
return this.$route.params.username;
}
}
}
2. 路由的方法
$router.push("路径")
:直接跳转到当前路径对应的路由上;
this.$router.push("/list");$router.back()
: 回退到上一次路由上;
this.$router.back();$router.go()
: 返回上一级;
this.$router.go(-1);
back和go的区别
- back():只能一级一级地返回;
- go(n):当go的参数n为正数时,表示前进n级;当n为负数时,表示返回n级;
replace和push的区别
router.replace跟 router.push 很像,唯一的不同就是,replace不会向 history 添加新记录,而是替换掉当前的 history 记录。
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/list">列表页</router-link>
<router-view></router-view>
</div>
<script src="../vue.js"></script>
<script src="../vue-router.js"></script>
<script>
let home = {
methods: {
goList(){
console.log(this);//this指向当前的组件
this.$router.push("/list");
}
},
template:`<div><button @click="goList">去列表页</button></div>`
}
let list = {
methods:{
goBack(){
this.$router.go(-1);//this.$router.back();
}
},
template:`<div><button @click="goBack">去首页</button></div>`
}
const routes = [{path:'/home',component:home},
{path:'/list',component:list}];
const router = new VueRouter({
routes
})
let vm = new Vue({
el:'#app',
router
})
</script>
3. 嵌套路由
- 在组件中,有
children属性
,属性值是一个数组
;里面配置了子路由;routes中子路由不需要带父路由的路径地址,同时不需要加/
;当子路由进行匹配时,会自动将父路由和/添加到子路由的前面,进行匹配; - 二级路由不能直接配置到routes表下面,应该找到它的父路由,
配置到父路由的children属性上
;否则,父路由也会消失;
let routes = [
{path:"/",component:home},
{path:"/home",component:home},
{path:"/list",component:list,children:[
{path:"detail",component:detail},
{path:"login",component:login}
]}
];
4. 命名路由
通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router 实例的时候,在 routes 配置中给某个路由设置名称。
const routes = [
{path:"/home",component:home,name:"first"}
]
要链接到一个命名路由,可以给 router-link 的 to改成动态的属性:to={name:组件的name名称}
<router-link :to="{name:'first'}">首页</router-link>
5. 编程式导航
在 Vue 实例内部,可以通过 $router
访问路由实例。因此可以调用 this.$router.push
。
在 Vue 实例外部通过router.push
来访问路由;
$router.push(“路径”)这个方法会向 history 栈添加一个新的记录,所以当用户点击浏览器后退按钮时,则回到之前的 URL。
push()参数的类型
-
字符串路径
this.$router.push("/list"); -
对象
this.$router.push({path:"/list"}); -
命名的路由
1)router.push({name:“list”,params:{id:‘123’}});
params传参在地址栏看不到id,但是其实参数已经传过去了;
2)router.push({name:“list”,query:{id:‘123’}});
query传参会拼接在地址栏中,可以看到id
- 带查询参数
1)router.push({path:"/list",query:{id:100}});
2)router.push({path:"/list",params:{id:100}}); 参数会被省略,传不过去
注意:同时使用path和params的时候,会导致params的参数会被忽略,参数传不过去;所以最好name和params一起使用;
或者使用下面的方式也可以传递参数:
router.push({ path: `/user/${id}` }) // -> /user/123
点击 <router-link :to="...">
等同于调用 router.push(...)
;所以上面同样的规则也适用于 router-link 组件的 to 属性。
6. 动态路由
动态路径参数
const routes = [{path:"/home/:id",component:home}]
路径冒号后面的是一个变量,这就是动态的路由;而且该路由会把对应的id放到$route的params属性上;属性值是路径实际的值;
<div id="app">
<router-link to="/home/1">第一页</router-link>
<router-link to="/home/2">第二页</router-link>
<router-link to="/home/abc">第三页</router-link>
<router-view></router-view>
</div>
<script src="../vue.js"></script>
<script src="../dist/vue-router.js"></script>
<script>
let home = {
beforeUpdate (to,from,next) { //校验这个用户是否有权限访问这个组件
if(to.params.id==1){
next('/home/2');
return;
}
next()
},
template:`<div>这是第{{$route.param.id}}页</div>`
}
const routes = [{path:"/home/:id",component:home}]
const router = new VueRouter({
routes
})
let vm = new Vue({
el:'#app',
router
})
</script>
当使用路由参数时,例如从/home/1导航到 /home/2,原来的组件实例会被复用,原来的组件实例会被复用。由于渲染的都是同一个home组件,所以组件不再销毁,当然也不再创建,复用了之前的组件,性能高; 但是生命周期钩子函数也不再执行;
复用组件时,想对路由参数的变化作出响应的话,可以简单地 watch (监测变化) $route 对象:
let home={
template:`<div>这是第{{$route.params.id}}本书</div>`,
watch:{
'$route'(to,from){
//to :要到达的当前的$route
// from : 上一个$route;
console.log(to);
console.log(from);
}
}
或者使用 beforeRouteUpdate 导航守卫
:
beforeRouteUpdate()的参数:
- to : 即将进入的目标路由对象
- from : 离开的路由对象
- next : 函数
- next();会立即进入目标路由
- next(false) : 中断当前的导航;
常规参数只会匹配被 / 分隔的 URL 片段中的字符。如果想匹配任意路径,我们可以使用通配符 (*):
{path:"*",redirect:"/person"},
7. 命名视图
有时候想同时 (同级) 展示多个视图,而不是嵌套展示;
例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。
如果 router-view 没有设置名字,那么默认为 default。
<!-- 如果该视图没有name,会显示default对应的组件 -->
<router-view></router-view>
<!--name="a"会显示components中属性名是a对应的组件-->
<router-view name="b"></router-view>
<router-view name="c"></router-view>
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s):
const routes = [
{path:"/home",component:zoo},
{path:"/list",components:{
default:foo,
b:bar,
c:zoo
}
}]
8. 重定向
通过配置routes中的redirect来设置重定向;
重定向
:当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b;
重定向的3种方式
//1.重定向
let routes = [{path:"*",redirect:"/home"}]
//2.重定向的目标也可以是一个命名的路由
let routes = [{path:"/a",redirect:{name:'home'}}]
//3.重定向的目标也可以是一个方法,动态返回重定向目标
let routes = [{path:"/a",redirect:to=>{
//方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}]
9. 路由传参
路由的传参方式:
- route-link的方式
<!-- 直接在路径后面拼接参数 -->
<router-link to="/home/12">首页</router-link>
//动态绑定路由
const routes = [{path:"/home/:id",component:home}]
- push
$router.push(`/list/5566`)
const routes = [{path:"/list/:id",component:list}]
- name组件params传参
this.$router.push({name:"list",params:{id:999}})
const routes = [{path:"/list",component:list,name:"list"}]
- path路径query传参
this.$router.push({path:'/list',query:{id:100}})
10. 导航守卫
全局前置守卫
使用 router.beforeEach 注册一个全局前置守卫:
router.beforeEach():全局的路由钩子函数;只要有路由发生改变,就会触发该函数;
router.beforeEach((to,from,next) => {
next()
})
当一个导航触发时,全局前置守卫按照创建顺序调用。
守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。
router.beforeEach((to,from,next) => {
console.log(100)
next()
})
router.beforeEach((to,from,next) => {
console.log(200)
next()
})
console.log(88)
//执行顺序是88、100、200 <-- 先执行同步,再执行异步;
//100和200都会同时执行,不会被覆盖;
每个守卫方法接收三个参数:
- to: Route:即将要进入的目标路由对象;
- from: Route:当前导航正要离开的路由;
- next:是一个函数,
一定要调用next方法来 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() 注册过的回调。
全局解析守卫
可以用 router.beforeResolve 注册一个全局守卫。
router.beforeResolve((to,from,next) => {
next()
})
这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
全局的守卫先执行,然后再执行组件内部的守卫,再执行beforeResolve的守卫;
全局后置钩子
router.afterEach((to, from) => {
// ...
})
钩子和守卫不同的是:钩子不会接受 next 函数也不会改变导航本身
;
组件内的守卫
在路由组件内定义的路由导航守卫:
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
let home = {
template: `<div>home</div>`,
beforeRouteEnter(to,from,next) {
//这个钩子函数执行前组件实例还没被创建,所以这个钩子函数中不能使用this
//this-->window
},
beforeRouteUpdate(to,from,next) {
//当组件被复用时,才会被调用;参数不一样,复用同一个组件时,会执行这个函数;这个可以使用this;this指向当前的实例;
// 举例来说,对于一个带有动态参数的路径/foo/:id,在/foo/1和 /foo/2之间跳转的时候,由于会渲染同样的Foo组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
},
beforeRouteLeave(to,from,next) {
//当离开这个组件时,钩子函数才会被调用;
// 可以访问组件实例 `this`
}
}
beforeRouteEnter守卫不能访问 this
,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,可以通过传一个回调给 next来访问组件实例
。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter(to,from,next) {
next(vm=>{
//当执行next时,该回调函数没有立即执行,等到当前组件实例创建好之后,再次触发的这个回调;
});
},
beforeRouteEnter 是支持给 next 传递回调的唯一守卫
。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。
beforeRouteUpdate (to, from, next) {
this.name = to.params.name
next()
}
离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消
。
beforeRouteLeave (to, from , next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
路由独享的守卫
在路由配置上直接定义 beforeEnter 守卫;
const routes = [{
path: '/home',
component: home,
beforeEnter: (to,from,next) => {}
}]
特点:当前home组件独享的守卫,只有调用home组件才会执行beforeEnter 守卫;
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。