目录
第五章
一、路由机制
vue-router是vue的一个插件,用来提供路由功能。通过路由的改变可以动态加载组件,达到开发单页面程序的目的
二、后端路由阶段
早期的网站开发整个HTML页面是由服务器来渲染的。服务器直接生产渲染好对应的HTML页面,返回给客户端进行展示.
1、后端路由
- 一个页面有自己对应的网址,也就是URL.
- URL会发送到服务器,服务器会通过正则对该URL进行匹配,并且最后交给一个Controller进行处理。
- Controller进行各种处理,最终生成HTML或者数据,返回给前端.
当我们页面中需要请求不同的路径内容时,交给服务器来进行处理,服务器渲染好整个页面,并且将页面返回给客户端.
这种情况下渲染好的页面,不需要单独加载任何的js和css,可以直接交给浏览器展示,这样也有利于SEO的优化.
2、后端路由的缺点
- —种情况是整个页面的模块由后端人员来编写和维护的.
- 另一种情况是前端开发人员如果要开发页面,需要通过PHP和Java等语言来编写页面代码.
- 通常情况下HTML代码和数据以及对应的逻辑会混在一起,编写和维护都是非常糟糕的事情.
三、前端路由阶段
1、前后端分离阶段︰
随着Ajax的出现,有了前后端分离的开发模式.
后端只提供API来返回数据,前端通过Ajax获取数据,并且可以通JavaScript将数据渲染到页面中.
这样做最大的优点就是前后端责任的清晰,后端专注于数据上,前端专注于交互和可视化上.
并且当移动端(iOS/Android)出现后,后端不需要进行任何处理,依然使用之前的一套API即可.
2、单页面富应用阶段:
其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由.也就是前端来维护下套路由规则.
3、前端路由的核心
改变URL,但是页面不进行整体的刷新。
四、Vue-Router的基本使用
vue-router是基于路由和组件的
- 路由用于设定访问路径,将路径和组件映射起来.
- 在vue-router的单页面应用中,页面的路径的改变就是组件的切换.
1.路由的安装
2.路由的使用
在html文件中使用时需要引入vueRouter(先引入vue.js,在引入vueRouter)
### js代码
<script>
// 声明组件
let myA = {
template: `
<div>A组件</div>
`
};
let myB = {
template: `
<div>B组件</div>
`
};
// 1.定义路由对象数组 route路由对象 router路由器对象 routes路由对象组
let routes = [{
path: '/a',
component: myA
}, {
path: '/b',
component: myB
}];
// 2.创建路由器对象 声明路由器实例对象
let router = new VueRouter({
// 声明配置路由对象
routes: routes
})
// 3.注册路由器对象
new Vue({
el: '#app',
// 将路由实例对象导入vue实例,相当于router:router,在此使用简写形式
router,
components: {
'my-a': myA,
'my-b': myB
},
data: {
msg: 'hello'
},
})
</script>
### html代码
<div id="app">
{{msg}}
<!-- 4.实现路由切换,router-link的本质是创建a标签 -->
<div>
<router-link to="/a">去A路由</router-link>
<router-link to="/b">去B路由</router-link>
<a href="#/a">a标签路由去A</a>
</div>
<div>
<!-- 路由组件显示的位置 路由出口,将匹配到的路由组件,渲染到此-->
<router-view></router-view>
</div>
</div>
let routes=[
let routes=[
{
path:"/",
//路由名称
name:"aRoute",
//别名 重命名
alias:'/aa',
//重定向
// redirect:'/a'
redirect:{name:'aRoute'}
}
]
3.动态路由的使用
需要把某种模式匹配到的所有路由,全部映射到同一个组件。
例如 : 有一个user组件,对于所有id不同的用户,都要使用这个组件来渲染,那么可以在vue-router的路由路径中使用动态路径参数来达到这个效果。
复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch $route 对象,或者使用组件内部的导航守卫
<div id="app">
{{msg}}
<!-- 4.使用路由 -->
<div>
<router-link to="/user/id/1/username/zhangsan">去A路由</router-link>
</div>
<div>
<!-- 路由组件显示的位置 -->
<router-view></router-view>
</div>
</div>
// 声明组件
let myA = {
data() {
return {
id: null,
username: ''
}
},
template: `
<div>A组件{{id}}--{{username}}</div>
`,
/* created() {
alert(1);
// console.log(this.$route);
this.id = this.$route.params.id;
this.username = this.$route.params.username;
} */
//监听
/* watch: {
msg(newValue, oldValue) { },
$route(to, from) {
// to是新路由 from是旧路由
// console.log(to, from);
this.id = this.$route.params.id;
this.username = this.$route.params.username;
}
} */
// 监听路由发生变化 路由守卫
beforeRouteUpdate(to, from, next) {
// console.log(to.params);
this.id = to.params.id;
this.username = to.params.username;
// 继续
// next();
// 阻断
// next(false)
}
};
let myB = {
template: `
<div>B组件</div>
`
};
// 1.定义路由对象数组 route路由对象 router路由器对象 routes路由对象组
let routes = [{
// 动态路由
// /user/id/1/username/zhangsan
// /user/id/2/username/lisi
path: '/user/id/:id/username/:username',
component: myA
}];
// 2.创建路由器对象
let router = new VueRouter({
routes: routes
})
// 3.注册路由器对象
new Vue({
el: '#app',
router: router,
components: {
'my-a': myA,
'my-b': myB
},
data: {
msg: 'hello'
},
methods: {}
})
4.嵌套路由
实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件
## js代码
<script>
// 创建一个组件
let user={
template:`
<div>
<router-link to='/userChild1'>子组件1</router-link>
<router-link to='/userChild2'>子组件2</router-link>
<router-view ></router-view>
</div>
`
}
let manager={
template:`
<div>管理员</div>
`
}
let userChild1={
template:`
<div>子组件1</div>
`
}
let userChild2={
template:`
<div>子组件2</div>
`
}
let routes=[{
path:'/manager',
component:manager
},{
path:'/user',
component:user,
redirect:'/userChild1',
children:[{
path:"/userChild1",
component:userChild1
},{
path:"/userChild2",
component:userChild2
}]
}];
let router=new VueRouter({
routes
})
new Vue({
el:"#app",
router,
data:{
}
})
### html代码
<div id="app">
<router-link to='user'>去用户</router-link>
<router-link to='manager'>去管理员</router-link>
<router-view></router-view>
</div>
五、编程式导航
除了使用 <router-link>
创建 a 标签来定义导航链接,还可以借助 router 的实例方法,通过编写代码来实现。this.$router.push()跳转到指定路由,会向history栈添加一个新的记录,当用户点击浏览器回退按钮的时候,可以回到跳转前的url。
### js代码
<script>
// 创建A B组件
let myA={
data(){
return {
}
},
template:`
<div>
A组件
</div>
`,
created(){
console.log(this.$route)
},
}
let myB={
data(){
return {
}
},
template:`
<div>
B组件
</div>
`
};
// 创建路由组件对象数组 routes router路由器对象 route路由对象
let routes=[
{
path:"/a",
component:myA,
name:'mya'
},
{
path:"/b",
component:myB,
}
];
// 创建路由器对象实例
let router=new VueRouter({
// 配置路由对象组
routes
})
new Vue({
el:'#app',
data:{
},
// 3.注册路由
router,
components:{
'my-a':myA,
'my-b':myB
},
methods:{
toPath(){
this.$router.push({
// 有效
path:"/a?id=1",
// 有效
// query:{id:1},
// // 无效
// params:{name:"terry"}
})
},
toPath1(){
this.$router.push({
name:'mya',
query:{id:1},
// 参数是一次性携带刷新页面 params失效
params:{
name:"terry"
}
})
}
}
})
</script>
### html代码
<div id="app">
<router-link to='/a'>去a路由</router-link>
<router-link to='/b'>去b路由</router-link>
<a href="#/a/2">去a路由</a>
<button @click="$router.push('/a')">跳转到A路由</button>
<button @click="$router.push('a')">跳转到A2路由</button>
<button @click="toPath">跳转到A3路由</button>
<button @click="toPath1">跳转到A4路由</button>
<router-view></router-view>
</div>
this.$router.repalce()这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步。
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
// 如果 history 记录不够用,则会报错
router.go(-100)
router.go(100)
六、路由组件传参
路由传递是指,从A页面跳转到B页面时,将A页面中的变量传递给B页面使用,传递参数的方式有两种 :
(1)path-query传参
使用path与query结合的方式传递参数时,参数会被拼接在浏览器地址栏中,并且刷新页面后数据也不会丢失。
### js代码
<script>
let user = {
template:`
<div @click="userHandler">普通用户,点击此处可跳转至管理员页面</div>
`,
data() {
return {
list:"hello",
obj:{
name:'tom',
age:3
}
}
},
methods: {
userHandler() {
// 跳转
this.$router.push({
path:'/manager',
query:{
list:this.list,
obj:JSON.stringify(this.obj)
}
})
}
},
}
let manager = {
template:`
<div>管理员 {{$route.query.list}} {{$route.query.obj}}</div>
`
}
let router = new VueRouter({
routes:[{
path:'/user',
component:user
},{
path:'/manager',
component:manager
}]
})
new Vue ({
el:"#app",
router
})
</script>
### html代码
<div id="app">
<router-link to="/user">user</router-link>
<router-link to="/manager">manager</router-link>
<router-view></router-view>
</div>
(2)name-params传参
name-params传参
### js代码
<script>
let user = {
template:`
<div @click="userHandler">普通用户,点击此处可跳转至管理员页面</div>
`,
data() {
return {
list:"hello",
obj:{
name:'tom',
age:3
}
}
},
methods: {
userHandler() {
// 跳转
this.$router.push({
name:'manager',
params:{
list:this.list,
obj:JSON.stringify(this.obj)
}
})
}
},
}
let manager = {
template:`
<div>管理员 {{$route.params.list}} {{$route.params.obj}}</div>
`
}
let router = new VueRouter({
routes:[{
path:'/user',
component:user,
name:'user'
},{
path:'/manager',
component:manager,
name:'manager'
}]
})
new Vue ({
el:"#app",
router
})
</script>
### html代码
<div id="app">
<router-link to="/user">user</router-link>
<router-link to="/manager">manager</router-link>
<router-view></router-view>
</div>
七、路由模式
(1)hash模式
hash模式的工作原理是hashchange事件,可以在window监听hash的变化。我们在url后面随便添加一个#xx触发这个事件。
window.onhashchange = function(event){ console.log(event); // 打印出一个HashChangeEvent事件对象,在该对象内有newURL和oldURL // location.hash中也有相关的信息 // 假设hash值是个颜色值,通过location.hash来获取到对应的hash值,然后设置页面中的某个元素的背景颜色来改变页面 }
(2)history模式
把window.history对象打印出来可以看到里边提供的方法和记录长度 history对象内有back(),forword(),go()等方法 前进,后退,跳转操作方法:
history.go(-3);//后退3次 history.go(2);//前进2次 history.go(0);//刷新当前页面 history.back(); //后退 history.forward(); //前进
(3)vue-router使用的模式
vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。如果不想要很丑的 hash,可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。不过这种模式要玩好,还需要后台配置支持。
hash路由和history路由的区别:
- hash路由在地址栏URL上有#,而history路由没有会好看一点
- 进行回车刷新操作,hash路由会加载到地址栏对应的页面,而history路由一般就404报错了(刷新是网络请求,没有后端准备时会报错)。
- hash路由支持低版本的浏览器,而history路由是HTML5新增的API。
- hash的特点在于它虽然出现在了URL中,但是不包括在http请求中,所以对于后端是没有一点影响的,所以改变hash不会重新加载页面,所以这也是单页面应用的必备。
- history运用了浏览器的历史记录栈,之前有back,forward,go方法,之后在HTML5中新增了pushState()和replaceState()方法(需要特定浏览器的支持),它们提供了对历史记录进行修改的功能,不过在进行修改时,虽然改变了当前的URL,但是浏览器不会马上向后端发送请求。
八、导航守卫--路由的改变会触发导航守卫
(1)全局守卫
全局守卫有全局前置守卫、全局后置守卫。
你可以使用 router.beforeEach
注册一个全局前置守卫:
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
可以使用 router.afterEach注册一个全局后置守卫:
const router = new VueRouter({ ... }) router.afterEach((to, from) => { // ... })
(2)全局解析守卫
你可以用 router.beforeResolve
注册一个全局守卫。这和 router.beforeEach
类似,因为它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。这里有一个例子,确保用户可以访问自定义 meta 属性 requiresCamera
的路由:
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... 处理错误,然后取消导航
return false
} else {
// 意料之外的错误,取消导航并把错误传给全局处理器
throw error
}
}
}
})
router.beforeResolve
是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。
(3)全局后置钩子
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})
它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。
它们也反映了 navigation failures 作为第三个参数:
router.afterEach((to, from, failure) => {
if (!failure) sendToAnalytics(to.fullPath)
})
(4)路由独享的守卫
你可以直接在路由配置上定义 beforeEnter
守卫:
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
beforeEnter
守卫 只在进入路由时触发,不会在 params
、query
或 hash
改变时触发。例如,从 /users/2
进入到 /users/3
或者从 /users/2#info
进入到 /users/2#projects
。它们只有在 从一个不同的 路由导航时,才会被触发。
你也可以将一个函数数组传递给 beforeEnter
,这在为不同的路由重用守卫时很有用:
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],
},
]
请注意,你也可以通过使用路径 meta 字段和全局导航守卫来实现类似的行为。
(5)组件内的守卫:
最后,你可以在路由组件内直接定义路由导航守卫(传递给路由配置的)
5.1可用的配置 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` 访问组件实例
})
}
注意 beforeRouteEnter
是支持给 next
传递回调的唯一守卫。对于 beforeRouteUpdate
和 beforeRouteLeave
来说,this
已经可用了,所以不支持 传递回调,因为没有必要了:
beforeRouteUpdate (to, from) {
// just use `this`
this.name = to.params.name
}
这个 离开守卫 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false
来取消。
beforeRouteLeave (to, from) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (!answer) return false
}
5.2使用组合 API
如果你正在使用组合 API 和 setup 函数来编写组件,你可以通过 onBeforeRouteUpdate
和 onBeforeRouteLeave
分别添加 update 和 leave 守卫。 请参考组合 API 部分以获得更多细节。