路由的本质就是一种对应关系,比如说我们在url地址中输入我们要访问的url地址之后,浏览器要去请求这个url地址对应的资源。
那么url地址和真实的资源之间就有一种对应的关系,就是路由。
后端路由
- 概念:根据不同的用户 URL 请求,返回不同的内容
- 本质:URL 请求地址与服务器资源之间的对应关系
前端路由
- 概念:根据不同的用户事件,显示不同的页面内容
- 本质:用户事件与事件处理函数之间的对应关系
SPA(Single Page Application)
- 后端渲染(存在性能问题)
- Ajax前端渲染(前端渲染提高性能,但是不支持浏览器的前进后退操作)
- SPA(Single Page Application)单页面应用程序:整个网站只有一个页面,内容的变化通过Ajax局部更新实现、同时支持浏览器地址栏的前进和后退操作
- SPA实现原理之一:基于URL地址的hash(hash的变化会导致浏览器记录访问历
史的变化、但是hash的变化不会触发新的URL请求)
- 在实现SPA过程中,最核心的技术点就是前端路由
Vue Router
基本使用
- 导入js文件
- 添加路由链接
- 添加路由占位符(最后路由展示的组件就会在占位符的位置显示)
- 定义路由组件
- 配置路由规则并创建路由实例
- 将路由挂载到Vue实例中
1 2 3 4 5
| <script src="./lib/vue_2.5.22.js"></script>
<script src="./lib/vue-router_3.0.2.js"></script>
|
1 2 3 4 5 6 7 8
| <div id="app"> <router-link to="/user">User</router-link> <router-link to="/login">Login</router-link>
<router-view></router-view> </div>
|
1 2 3
| var User = { template:"<div>This is User</div>" } var Login = { template:"<div>This is Login</div>" }
|
1 2 3 4 5 6 7 8 9 10
| var myRouter = new VueRouter({ routes:[ {path:"/user",component:User}, {path:"/login",component:Login} ] })
|
1 2 3 4 5 6
| new Vue({ el:"#app", router:myRouter })
|
嵌套路由
1 2 3 4 5 6
| <div id="app"> <router-link to="/user">User</router-link> <router-link to="/login">Login</router-link>
<router-view></router-view> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| var User = { template: "<div>This is User</div>" }
var Login = { template: `<div> <h1>This is Login</h1> <hr> <router-link to="/login/account">账号密码登录</router-link> <router-link to="/login/phone">扫码登录</router-link> <!-- 子路由组件将会在router-view中显示 --> <router-view></router-view> </div>` }
var account = { template:"<div>账号:<input><br>密码:<input></div>"}; var phone = { template:"<h1>扫我二维码</h1>"}; var myRouter = new VueRouter({ routes: [ { path:"/",redirect:"/user"}, { path: "/user", component: User }, { path: "/login", component: Login, children:[ { path: "/login/account", component: account }, { path: "/login/phone", component: phone }, ] } ] })
var vm = new Vue({ el: '#app', data: {}, methods: {}, router:myRouter });
|
动态路由匹配
1 2 3 4 5 6 7 8 9 10
| var User = { template:"<div>用户:{{$route.params.id}} 名字:{{$route.params.name}}</div>"}
var myRouter = new VueRouter({ routes: [ { path: "/user/:id", component: User }, ] })
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| var User = { props:["id"], template:"<div>用户:{{id}}</div>" }
var myRouter = new VueRouter({ routes: [ { path: "/user/:id", component: User,props:true }, ] })
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| var User = { props:["username","pwd"], template:"<div>用户:{{username}}---{{pwd}}</div>" }
var myRouter = new VueRouter({ routes: [ { path: "/user/:id", component: User,props:{username:"jack",pwd:123} }, ] })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var User = { props:["username","pwd","id"], template:"<div>用户:{{id}} -> {{username}}---{{pwd}}</div>" }
var myRouter = new VueRouter({
routes: [ { path: "/user/:id", component: User, props:(route)=>{ return {username:"jack",pwd:123,id:route.params.id} } }, ] })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@click="fn(value)"
fn(value){ this.$router.push("/user/"+JSON.string(value)); }
{ path: "/user/:value", component: User,props:true },
User = { props:['value'], data(){ return { obj: JSON.parse(this.value) } } }
|
命名路由
1 2 3 4 5 6 7
| var myRouter = new VueRouter({ routes: [ { path: "/user/:id", component: User, name:"user"}, ] })
|
1 2 3 4 5 6
| <router-link to="/user">User</router-link> <router-link :to="{ name:'user' , params: {id:123} }">User</router-link>
myRouter.push( { name:'user' , params: {id:123} } )
|
重定向和别名
重定向
1 2 3 4 5
| const router = new VueRouter({ routes: [ { path: '/a', redirect: '/b' } ] })
|
1 2 3 4 5
| const router = new VueRouter({ routes: [ { path: '/a', redirect: { name: 'foo' }} ] })
|
1 2 3 4 5 6 7 8
| const router = new VueRouter({ routes: [ { path: '/a', redirect: to => { }} ] })
|
别名
/a
的别名是 /b
,意味着,当用户访问 /b
时,URL 会保持为 /b
,但是路由匹配则为 /a
,就像用户访问 /a
一样。
1 2 3 4 5
| const router = new VueRouter({ routes: [ { path: '/a', component: A, alias: '/b' } ] })
|
路由懒加载
把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
1
| const Foo = () => import('./Foo.vue')
|
1 2 3 4 5
| const router = new VueRouter({ routes: [ { path: '/foo', component: Foo } ] })
|
把组件按组分块
有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。
Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。
1 2 3
| const Foo = () => import( './Foo.vue') const Bar = () => import( './Bar.vue') const Baz = () => import( './Baz.vue')
|
编程式导航
其实就是用编程的方式控制路由跳转
页面导航的两种方式:
A.声明式导航:通过点击链接的方式实现的导航
B.编程式导航:调用js的api方法实现导航
Vue-Router中常见的导航方式:
1 2 3 4 5 6 7 8 9
| this.$router.push("hash地址"); this.$router.push("/login"); this.$router.push({ name:'user' , params: {id:123} }); this.$router.push({ path:"/login" }); this.$router.push({ path:"/login",query:{username:"jack"} });
this.$router.go( n ); this.$router.go( -1 ); this.$router.back();
|
导航守卫
官网地址
vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的
参数或查询的改变并不会触发进入/离开的导航守卫
全局前置守卫
1 2 3 4 5
| const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => { })
|
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
每个守卫方法接收三个参数:
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()
注册过的回调。
确保 next
函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。
全局解析守卫
在 2.5.0+ 你可以用 router.beforeResolve
注册一个全局守卫。这和 router.beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
全局后置钩子
1 2 3
| router.afterEach((to, from) => { })
|
路由独享的守卫
1 2 3 4 5 6 7 8 9 10 11
| const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { } } ] })
|
组件内的守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const Foo = { template: `...`, beforeRouteEnter (to, from, next) { }, beforeRouteUpdate (to, from, next) { }, beforeRouteLeave (to, from, next) { } }
|
beforeRouteEnter
守卫 不能 访问 this
,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next
来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
1 2 3 4 5
| beforeRouteEnter (to, from, next) { next(vm => { }) }
|
注意 beforeRouteEnter
是支持给 next
传递回调的唯一守卫。对于 beforeRouteUpdate
和 beforeRouteLeave
来说,this
已经可用了,所以不支持传递回调,因为没有必要了。
1 2 3 4 5
| beforeRouteUpdate (to, from, next) { this.name = to.params.name next() }
|
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false)
来取消。
1 2 3 4 5 6 7 8
| beforeRouteLeave (to, from, next) { const answer = window.confirm('Do you really want to leave? you have unsaved changes!') if (answer) { next() } else { next(false) } }
|
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。
- 调用全局的
beforeEach
守卫。
- 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。
- 在路由配置里调用
beforeEnter
。
- 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。
- 调用全局的
beforeResolve
守卫 (2.5+)。
- 导航被确认。
- 调用全局的
afterEach
钩子。
- 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给 next
的回调函数,创建好的组件实例会作为回调函数的参数传入。