参考:https://blog.csdn.net/qq_31967569/article/details/91546294
目录
3、前端路由的两种实现方式:基于hash 和基于history
4.3 方案三:使用webpack的require.ensure技术,也可以实现按需加载。
8、Vue-router跳转和location.href有什么区别
1、对路由的理解?
路由,其实就是指向的意思。当我点击页面上的home按钮时,页面中就要显示home的内容,如果点击页面上的about 按钮,页面中就要显示about 的内容。Home按钮 => home 内容, about按钮 => about 内容,也可以说是一种映射. 所以在页面上有两个部分,一个是点击部分,一个是点击之后,显示内容的部分。
点击之后,怎么做到正确的对应,比如,我点击home 按钮,页面中怎么就正好能显示home的内容。这就要在js 文件中配置路由。
路由中有三个基本的概念 route, routes, router:
- route(路由信息对象):它是一条路由,由这个英文单词可看出 它是单数,Home按钮 => home内容, 这是一条route, about按钮 => about 内容,这是另一条路由。route对象 {path:’/home’, component: home}。包括 path,params,hash,query,fullPath,matched,name 等路由信息参数;
- routes:是一组路由,把上面的每一条路由组合起来,形成一个数组。[{home 按钮 =>home内容 }, { about按钮 => about 内容}];
- router(路由实例对象): 是一个机制,相当于一个管理者,它来管理路由。因为routes 只是定义了一组路由,它放在哪里是静止的,当真正来了请求,怎么办? 就是当用户点击home 按钮的时候,怎么办?这时router 就起作用了,它到routes 中去查找,去找到对应的 home 内容,所以页面中就显示了 home 内容。包括了路由的跳转方法,钩子函数等。
- 客户端中的路由:实际上就是dom 元素的显示和隐藏。直接找到与地址匹配的一个组件或对象并将其渲染出来。改变浏览器地址而不向服务器发出请求。当页面中显示home 内容的时候,about 中的内容全部隐藏,反之也是一样。客户端路由有两种实现方式:基于hash 和基于history
2、路由的实现过程
在vue中实现路由还是相对简单的。因为页面中所有内容都是组件化的,我们只要把路径和组件对应起来就可以了,然后在页面中把组件渲染出来。
- <router-link>:对应点击部分。to属性表示定义点击之后,要到哪里去。例 点击Home后,跳转到/home :<router-link to="/home">Home</router-link>
- <router-view>:对应显示部分。其实它就是一个占位符,它在什么地方,匹配路径的组件就显示在什么地方。<router-view></router-view>
<template>
<div id="app">
<header>
<!-- router-link 定义点击后导航到哪个路径下 -->
<router-link to="/home">Home</router-link>
<router-link to="/about">About</router-link>
</header>
<!-- 对应的组件内容渲染到router-view中 -->
<router-view></router-view>
</div>
</template>
(1)在js中配置路由。
首先要定义route对象, 一条路由的实现。由两个部分组成: 路径path和对应的组件component. 如:route对象 {path:’/home’, component: home}。两条路由,组成一个routes:
const routes = [
{ path: '/home', component: Home }, //访问路径/home时,显示Home组件
{ path: '/about', component: About } //访问路径/about时,显示About组件
]
(2)创建router 对路由进行管理。它由构造函数 new vueRouter() 创建,接受routes 参数
const router = new VueRouter({
routes // routes: routes 的简写
})
(3)配置完成后,把router 实例注入到 vue 根实例中,就可以使用路由了
const app = new Vue({
router
}).$mount('#app')
执行过程:
- 当用户点击 router-link 标签时,会去寻找它的 to 属性;
- 它的 to 属性和 js 中配置的路径 { path: '/home', component: Home} 对应,从而找到了匹配的组件;
- 最后把组件渲染到 <router-view> 标签所在的地方。
3、前端路由的两种实现方式:基于hash 和基于history
3.1 后端路由
路由这个概念最先是后端出现的,后端路由又称服务器路由。
服务器接收前端发来的http请求后,会根据url找到相应的映射函数,然后执行函数,并将返回值发回给客户端。
对于静态资源来说,映射函数就是一个文件读取操作;对于动态资源,映射函数可能是一个数据库读取操作,或者是数据处理操作;根据读取的数据,在服务端使用相应的模板来渲染页面,再返回渲染完毕的页面【⚠️这是早期的做法】
在浏览器输入网址后发起请求,返回来的 HTML
页面是最终呈现的效果(如下图)。并且每次点击页面跳转,都会重新访问服务器,然后服务器返回HTML
资源。
-
好处:安全性好、SEO好;(SEO 搜索引擎优化)
-
缺点:加大服务器压力,不利于用户体验
3.2 hash模式
(1)简介:开发中默认的模式,它的URL带着一个#,例如:www.abc.com/#/vue,它的hash值就是#/vue;
“www.baidu.com/#/hashhash” ,它的hash值就是 “#/hashhash” 。(此 hash 不是密码学里的散列运算)早期的前端路由的实现就是基于location.hash来实现的。
(2)特点:hash值会出现在URL里面,但是不会出现在HTTP请求中,对后端完全没有影响。所以改变hash值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的IE浏览器也支持这种模式。hash路由被称为是前端路由,已经成为SPA(单页面应用)的标配。
(3)原理: hash模式的主要原理是onhashchange 事件。在当前 URL 的哈希值(以 '#' 号为开始) 发生改变时触发onhashchange 。使用hash模式时,我们可以考虑对每个路由注册一个映射函数,页面加载时,在window注册onhashchange事件的监听函数,每次切换url,就调用对应的映射函数。如下图,当hash值变化时,触发onhaschange事件。
触发hash变化的方式主要有两种:
- 直接修改url中的hash值;
- 点击后退或前进按钮时,hash值发生变化;
- 通过a标签,并为a设置href属性,当用户点击这个标签后,URL就会发生改变;
- 直接使用JavaScript来对loaction.hash进行赋值,从而改变当前url的hash值,触发onhashchange事件。
例如:点击按钮时,触发click事件,修改哈希值。
使用onhashchange()事件的好处:
- 在页面的hash值发生变化时,无需向后端发起请求,window就可以监听事件的改变,并按规则加载相应的代码;
- hash值变化对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的前进和后退。虽然是没有请求后端服务器,但是页面的hash值和对应的URL关联起来了。
(4)如何获取页面的hash变化
1)通过watch 监听$route的变化
// 监听,当路由发生变化的时候执行
watch: {
$route: {
handler: function(val, oldVal){
console.log(val);
},
// 深度观察监听
deep: true
}
},
2)window.location.hash读取#值 。window.location.hash 的值可读可写,读取来判断状态是否改变,写入时可以在不重载网页的前提下,添加一条历史访问记录 。
补充:使用hash模式的vue项目,当切换组件时,组件对应的路径会在hash值部分显示。
例如切换到组件User
3.3 history模式
到了HTML5,又提供了History API来实现URL的变化。其中做最主要的API有以下两个:history.pushState()和history.repalceState()。
(1)简介:history模式的URL中没有#,它使用的是传统的路由分发模式,即用户在输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。
(2)特点:当使用history模式时,URL就像这样:abc.com/user/id。相比hash模式更加好看。但是,history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。所以,需要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回单页应用的 html 文件。
(3)History主要的API分为两大部分,切换历史状态和修改历史状态:
- 修改历史状态:包括了 HTML5 History Interface 中新增的
pushState()
和replaceState()
方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们修改了url时,但浏览器不会立即向后端发送请求(即不进行刷新)。如果要做到改变url但又不刷新页面的效果,就需要前端用上这两个API。
- pushState() : 新增一个历史记录
replaceState()
: 直接替换当前的历史记录
这两个api都接受三个参数:
window.history.pushState(state,title, "http://www.163.com");
- state:合法的 Javascript 对象,可以用在 popstate 事件中;
- title:现在大多浏览器忽略这个参数,可以直接用 null 代替;
- url:任意有效的 URL,用于更新浏览器的地址栏。
- 切换历史状态: 包括
forward()
、back()
、go()
三个方法,对应浏览器的前进,后退,跳转操作。
History.back()
:移动到上一个网址,等同于点击浏览器的后退键。对于第一个访问的网址,该方法无效果。History.forward()
:移动到下一个网址,等同于点击浏览器的前进键。对于最后一个访问的网址,该方法无效果。History.go()
:接受一个整数作为参数,以当前网址为基准,移动到参数指定的网址,比如go(1)
相当于forward()
,go(-1)
相当于back()
。如果参数超过实际存在的网址范围,该方法无效果;如果不指定参数,默认参数为0
,相当于刷新当前页面。
(4)History.pushState()
方法用于在历史中添加一条记录。
pushState()
方法不会触发页面刷新,只是导致地址栏发生变化。
state
:一个与添加的记录相关联的状态对象,主要用于popstate
事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填null
。title
:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。url
:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
假定当前网址是example.com/1.html
,使用pushState()
方法在浏览记录(History 对象)中添加一个新记录。
var stateObj = { foo: 'bar' };
history.pushState(stateObj, 'page 2', '2.html');
添加新记录后,浏览器地址栏立刻显示example.com/2.html
,但并不会跳转到2.html
,甚至也不会检查2.html
是否存在,它只是成为浏览历史中的最新记录。
这时,在地址栏输入一个新的地址(比如访问google.com
),此时历史url为 2.html、1.html;
然后点击了倒退按钮,页面的 URL 将显示2.html
;你再点击一次倒退按钮,URL 将显示1.html
。
(5)History.replaceState()
方法用来修改 History 对象的当前记录,其他都与pushState()
方法一模一样。
假定当前网页是example.com/example.html
。
history.pushState({page: 1}, 'title 1', '?page=1')
// URL 显示为 http://example.com/example.html?page=1
history.pushState({page: 2}, 'title 2', '?page=2');
// URL 显示为 http://example.com/example.html?page=2
此时历史记录包含: page=2、page=1、example.html
history.replaceState({page: 3}, 'title 3', '?page=3');
// URL 显示为 http://example.com/example.html?page=3
用page3替换了page2 此时历史记录包含:page3、page=1、example.html
history.back()
// URL 显示为 http://example.com/example.html?page=1
从当前页page3 返回到了 page1
history.back()
// URL 显示为 http://example.com/example.html
从当前页page1 返回到了 example.html
history.go(2)
// URL 显示为 http://example.com/example.html?page=3
(6)popstate 事件
每当同一个文档的浏览历史(即history
对象)出现变化时,就会触发popstate
事件。
注意:
- 仅仅调用
pushState()
方法或replaceState()
方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用History.back()
、History.forward()
、History.go()
方法时才会触发。 - 另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。
参考:History 对象 -- JavaScript 标准参考教程(alpha)
(7)对于单页应用的 history 模式而言,url 的改变有三种方式:
- 点击浏览器的前进或后退按钮
- 点击 a 标签
- 在 JS 中主动触发 history.pushState 和replaceState函数。
方式1,3都能触发popstate ,所以我们只需要对a标签处理,通过对取消a标签的默认行为,然后调用pushState来触发路径变化。
(5)虽然history模式丢弃了丑陋的#。但是,它也有自己的缺点,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出404来。
如果想要切换到history模式,就要进行以下配置(后端也要进行配置):
const router = new VueRouter({
mode: 'history',
routes: [...]
})
3.4 两种模式对比
调用 history.pushState() 相比于直接修改 hash,存在以下优势:
- pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;
- pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
- pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
- pushState() 可额外设置 title 属性供后续使用。
- hash模式下,仅hash符号之前的url会被包含在请求中,后端如果没有做到对路由的全覆盖,也不会返回404错误;history模式下,前端的url必须和实际向后端发起请求的url一致,如果没有对用的路由处理,将返回404错误。
- 使用浏览器的前进,后退键的时候会重新发送请求,没有合理地利用缓存。
hash模式和history模式都有各自的优势和缺陷,还是要根据实际情况选择性的使用。
3.5 原生JS版前端路由实现
(1)基于 hash 实现
HTML 部分:
<body>
<ul>
<!-- 定义路由 -->
<li><a href="#/home">home</a></li>
<li><a href="#/about">about</a></li>
<!-- 渲染路由对应的 UI -->
<div id="routeView"></div>
</ul>
</body>
JavaScript 部分:
// 页面加载完不会触发 hashchange,这里主动触发一次 hashchange 事件
window.addEventListener('DOMContentLoaded', onLoad)
// 监听路由变化
window.addEventListener('hashchange', onHashChange)
// 路由视图
var routerView = null
function onLoad () {
routerView = document.querySelector('#routeView')
onHashChange()
}
// 路由变化时,根据路由渲染对应 UI
function onHashChange () {
switch (location.hash) {
case '#/home':
routerView.innerHTML = 'Home'
return
case '#/about':
routerView.innerHTML = 'About'
return
default:
return
}
}
(2)基于 history 实现
HTML 部分:
<body>
<ul>
<li><a href='/home'>home</a></li>
<li><a href='/about'>about</a></li>
<div id="routeView"></div>
</ul>
</body>
JavaScript 部分:
// 页面加载完不会触发 hashchange,这里主动触发一次 hashchange 事件
window.addEventListener('DOMContentLoaded', onLoad)
// 监听路由变化
window.addEventListener('popstate', onPopState)
// 路由视图
var routerView = null
function onLoad () {
routerView = document.querySelector('#routeView')
onPopState()
// 拦截 <a> 标签点击事件默认行为, 点击时使用 pushState 修改 URL并更新手动 UI,从而实现点击链接更新 URL 和 UI 的效果。
var linkList = document.querySelectorAll('a[href]')
linkList.forEach(el => el.addEventListener('click', function (e) {
e.preventDefault()
history.pushState(null, '', el.getAttribute('href'))
onPopState()
}))
}
// 路由变化时,根据路由渲染对应 UI
function onPopState () {
switch (location.pathname) {
case '/home':
routerView.innerHTML = 'Home'
return
case '/about':
routerView.innerHTML = 'About'
return
default:
return
}
}
每当处于激活状态的历史记录条目发生变化时,popstate
事件就会在对应window
对象上触发. 如果当前处于激活状态的历史记录条目是由
history.pushState()
方法创建,或者由history.replaceState()方法修改过
的, 则popstate事件对象的
state
属性包含了这个历史记录条目的state对象的一个拷贝.
但是,调用history.pushState()
或者history.replaceState()
不会触发popstate事件. popstate
事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮(或者在JavaScript中调用history.back()、history.forward()、history.go()
方法).
参考:前端路由原理解析和实现_S筱潇S四维Smile-CSDN博客
4、Vue-Router 的懒加载如何实现
Vue 是单页面应用,可能会有很多的路由引入 ,这样使用 webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力。这样就更加高效了。这样会大大提高首屏显示的速度,但是可能其他的页面的速度就会降下来。
4.1 方案一(常用):使用箭头函数+import动态加载
import通过这种特殊注释语法,将几个路由放到一个组中 :
// 这三个组件分为一组:login_home_welcome
const Login = () => import(/* webpackChunkName: "login_home_welcome" */ '@/components/Login.vue')
const Home = () => import(/* webpackChunkName: "login_home_welcome" */ '@/components/Home.vue')
const Welcome = () => import(/* webpackChunkName: "login_home_welcome" */ '@/components/Welcome.vue')
const router = new Router({
routes: [
{ path: '/login', component: Login },
{ path: '/home', component: Home },
{ path: '/welcome', component: Welcome }
]
(1)安装开发依赖@babel/plugin-syntax-dynamic-import
(2)在配置文件babel.config.js中声明该插件
(3)将路由改为按需加载的形式
其中 import(/*分组名:划分大同一分组的多个组件*/ 路由组件)
4.2 方案二:使用箭头函数+require动态加载
const router = new Router({
routes: [
{
path: '/list',
component: resolve => require(['@/components/list'], resolve)
}
]
})
4.3 方案三:使用webpack的require.ensure技术,也可以实现按需加载。
这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件
//require.ensure(当前需要进来的模块的一些依赖, 回调函数 动态引入其他模块, 打包的组块名称)
const Foo = resolve => require.ensure([], () => resolve(require('./Foo.vue')), 'group-foo')
const Bar = resolve => require.ensure([], () => resolve(require('./Bar.vue')), 'group-foo')
const Baz = resolve => require.ensure([], () => resolve(require('./Baz.vue')), 'group-foo')
require.ensure(
dependencies:String [],
callback:function(require),
errorCallback:function(error),
chunkName:String
)
require.ensure()接受四个参数:
- 第一个参数:表依赖关系,是一个数组,代表了当前需要进来的模块的一些依赖;
- 第二个参数: 是一个回调函数。这个有一个参数要求,通过回调函数的require可以动态引入组件。虽然require是回调函数的参数,但是其名称实际上是不能换的,否则WebPack就无法静态分析的时候处理它;
- 第三个参数:errorCallback比较好理解,就是处理错误的回调;
- 第四个参数:chunkName则是指定打包的组块名称。
5、 如何定义动态路由?如何获取传过来的动态参数?
5.1 param方式
- 配置路由格式:使用冒号:绑定动态参数。
/user/:userid
const routes = [{ //配置路由的index.js文件
path: '/user/:userid',
name: 'users',
component: User
}]
- 传递的方式:在path后面跟上对应的值 '/user/'+id
<router-link :to="'/user/'+id" >用户</router-link>
- 传递后形成的路径:
/router/123
(1)路由定义
const routes = [{ //配置路由的index.js文件
path: '/user/:userid',
name: 'users',
component: User
}]
(2)路由跳转
1)方法一 使用router-link实现路由跳转:使用字符串,在路径后面直接跟上对应的值
属性名userId使用自己的id,不必与路由配置的路径中一致
<router-link :to="'/user/'+userId" replace>用户</router-link>
2)方法二 使用router-link实现路由跳转:使用对象的方式。
name: 'users' 的属性名和属性值 必须跟配置路由中的一致
params中的属性名userid 必须与配置路由path中的'/user/:userid'一致
<router-link :to="{ name: 'users', params: { userid: userId}}">用户</router-link>
配置路由的index.js文件中,需定义name属性,才可使用方法二
3)方法三:使用$router的方式进行路由的跳转
this.$router.push('/user/' + this.userId)
// 或
this.$router.push({
name: 'users', //属性名和属性值 必须与路由一致
params: { // id属性名 必须与路由一致
userid: this.userId
}
})
配置路由的index.js文件中,需定义name属性,才可使用方法三
(3)参数获取
1)方式一:通过 $route.params.userid
获取传递的值(userid
表要查询的参数)。
2)方式二:组件中也可以用props
来接受参数,前提是需要在路由配置中设置props为true。
const routes = [{ //配置路由的index.js文件
path: '/user/:userid',
name: 'users',
component: User,
//ture,代表将path后面的参数作为值,传递到组件中,组件中通过props属性接受这个值
props:true
}]
在组件中通过props接收参数
<script> // 组件中
export default {
props:{
name:{
type:String,
default:'lily' //默认情况
},
id:{
type:Number,
default:'0' //默认情况
}
}
}
</script>
3)$router 和 $route的区别
- $router : 是路由操作对象,只写对象
- $route : 路由信息对象,只读对象
5.2 query方式
- 配置路由格式:
/router
,也就是普通配置
const routes = [{ //配置路由的index.js文件
path: '/user',
name: 'users',
component: User
}]
- 传递的方式:对象中使用query的key作为传递方式。
注意:query传参的方式只可以通过对象,不可以使用字符串。
里面的属性名(如本例中的id)可以随便起名,不像params方式传参时受限。
<router-link :to="{path:'/user',query:{id:18, name:'sh'}">用户</router-link>
- 传递后形成的路径:
/route?id=18 & name=sh。
参数之间用&符号连接
localhost:8080/user?id=18 & name=sh
(1)路由定义(普通的路由配置)
const routes = [{ //配置路由的index.js文件
path: '/user',
name: 'users',
component: User
}]
(2)路由跳转
1)方法一 使用router-link方式实现路由跳转:使用name
属性名name 和 其属性值 需与路由配置中的 name: 'users' 一致
<router-link :to="{ name: 'users', query: { uname: james }}">按钮</router-link>
2)方法二 使用router-link方式实现路由跳转:使用path
<router-link :to="{ path: '/user', query: { uname:james }}">按钮</router-link>
3)方法三 使用$router的方式进行路由的跳转
// 方式1 使用name
this.$router.push({ name: 'users', query:{ uname:james }})
// 方式2 使用路径
this.$router.push({ path: '/user', query:{ uname:james }})
// 方式3 直接将传的参添加到路径后
this.$router.push('/user?uname=' + jsmes)
(3)参数获取
1)方式一:通过 $route.query.userid
获取传递的值(userid
表要查询的参数)。
2)方式二:组件中也可以用props
来接受参数,前提是需要在路由配置中设置props为true。
与params中的方式二一样。
5.3 params和query的区别?
- params方式路由的引入只能用name;query方式路由的引入可以用name和path。
params的参数是URL不可或缺的一部分,一定
要加路由后面添加参数path: '/user/:userid'
,不添加刷新页面数据会丢失;而query是拼接在url后面的参数,路由后面不添加也没关系。- params 在浏览器地址栏中不显示参数;query则显示。params相当于post请求,参数不会在地址栏中显示;query相当于get请求,页面跳转的时候,可以在地址栏看到请求参数。
-
params在浏览器地址栏中不显示参数名称
http://47.107.171.252:8001/#/detail/123456
query在浏览器地址栏中显示参数名称
http://47.107.171.252:8001/#/detail?id=123456
6、Vue-router 导航守卫
- 全局的守卫:beforeEach(全局前置)、beforeResolve(全局解析)、afterEach(全局后置);写在router.js 文件中;
- 路由独享的守卫:beforeEnter;写在router单个路由中;
- 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave;写在组件中。
优先级:①>②>③,组件内的守卫优先级是最低的
6.1 全局的守卫
(1)beforeEach(全局前置守卫)
对用于访问网页的情况进行相应的跳转。例如初次登入,不能访问除登录页面外的页面,已登录会自动跳转自首页等等
const router = new VueRouter({...})
//使用router.beforeEach注册一个全局前置守卫
router.beforeEach((to,from,next) =>{
//...
})
- to: 将要访问的路径;
- from:当前导航正要离开的路由;
- next 是一个函数,表示放行。 next() 放行、next('/login') 强制跳转到到该路径。 确保要调用next方法,否则钩子就不会被resolved 。
(2)beforeResolve(全局解析)
这和router.beforeEach类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用
(3)afterEach(全局后置)
不同的是,后置守卫不会接收next函数,也不会改变导航本身
router.afterEach((to,from)=>{
//...
})
6.2 路由独享的守卫
单个路由独享的守卫,可以在单个路由中直接定义(方法、参数等都和全局前置守卫beforeEach一样)
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}]})
6.3 组件内的守卫
都有三个参数:to, from, next
- beforeRouteEnter:路由进入之前调用。不能获取组件实例 `this` ,因为当守卫执行前,组件实例还没被创建。
- beforeRouteUpdate:路由更新调用。在当前路由改变,但是该组件被复用时调用。
- beforeRouteLeave:导航离开该组件的对应路由时调用。用来禁止用户在还未保存修改前突然离开。
watch: {
$route(to, from) {
//当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象
// 对路由变化作出响应...
console.log(" // 对路由变化作出响应...");
}
}, //监听
beforeRouteEnter(to, from, next) {
//路由进入之前调用
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
//console.log(from);//上一个激活的路由
//console.log(to);//当前路由
next(vm => {
// 通过 `vm` 访问组件实例
console.log(vm)//当前激活的路由对应的组件对象
});
// next();
},
beforeRouteUpdate(to, from, next) {
//路由更新调用
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
// console.log(to);
next();
},
beforeRouteLeave(to, from, next) {
//这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
//to是将要去的路由
//from是将要离开的路由(也就是当前的路由)
const answer = window.confirm(
"Do you really want to leave? you have unsaved changes!"
);
if (answer) {
next();
} else {
next(false);
}
},
补充:钩子函数
- 是个函数,在系统消息触发时被系统调用 ,在系统级对所有消息、事件进行过滤;
- 不是用户自己触发的
7、对前端路由的理解
在前端技术早期,一个 url 对应一个页面,如果要从 A 页面切换到 B 页面,那么必然伴随着页面的刷新。这个体验并不好,不过在最初也是无奈之举——用户只有在刷新页面的情况下,才可以重新去请求数据。
后来,改变发生了——Ajax 出现了,它允许人们在不刷新页面的情况下发起请求;与之共生的,还有“不刷新页面即可更新页面内容”这种需求。在这样的背景下,出现了 SPA(单页面应用)。
SPA极大地提升了用户体验,它允许页面在不刷新的情况下更新页面内容,使内容的切换更加流畅。但是在 SPA 诞生之初,人们并没有考虑到“定位”这个问题——在内容切换前后,页面的 URL 都是一样的,这就带来了两个问题:
- SPA 其实并不知道当前的页面“进展到了哪一步”。可能在一个站点下经过了反复的“前进”才终于唤出了某一块内容,但是此时只要刷新一下页面,一切就会被清零,必须重复之前的操作、才可以重新对内容进行定位——SPA 并不会“记住”你的操作。
- 由于有且仅有一个 URL 给页面做映射,这对 SEO 也不够友好,搜索引擎无法收集全面的信息
为了解决这个问题,前端路由出现了。
前端路由可以帮助我们在仅有一个页面的情况下,“记住”用户当前走到了哪一步——为 SPA 中的各个视图匹配一个唯一标识。这意味着用户前进、后退触发的新内容,都会映射到不同的 URL 上去。此时即便他刷新页面,因为当前的 URL 可以标识出他所处的位置,因此内容也不会丢失。
那么如何实现这个目的呢?首先要解决两个问题:
- 当用户刷新页面时,浏览器会默认根据当前 URL 对资源进行重新定位(发送请求)。这个动作对 SPA 是不必要的,因为我们的 SPA 作为单页面,无论如何也只会有一个资源与之对应。此时若走正常的请求-刷新流程,反而会使用户的前进后退操作无法被记录。
- 单页面应用对服务端来说,就是一个URL、一套资源,那么如何做到用“不同的URL”来映射不同的视图内容呢?
从这两个问题来看,服务端已经完全救不了这个场景了。所以要靠咱们前端自力更生,不然怎么叫“前端路由”呢?作为前端,可以提供这样的解决思路:
- 拦截用户的刷新操作,避免服务端盲目响应、返回不符合预期的资源内容。把刷新这个动作完全放到前端逻辑里消化掉。
- 感知 URL 的变化。这里不是说要改造 URL、凭空制造出 N 个 URL 来。而是说 URL 还是那个 URL,只不过我们可以给它做一些微小的处理——这些处理并不会影响 URL 本身的性质,不会影响服务器对它的识别,只有我们前端感知的到。一旦我们感知到了,我们就根据这些变化、用 JS 去给它生成不同的内容。
8、Vue-router跳转和location.href有什么区别
- 使用
location.href= /url
来跳转,简单方便,但是刷新了页面; - 使用
history.pushState( /url )
,无刷新页面,静态跳转; - 引进 router ,然后使用
router.push( /url )
来跳转,使用了diff
算法,实现了按需加载,减少了 dom 的消耗。其实使用 router 跳转和使用history.pushState()
没什么差别的,因为vue-router就是用了history.pushState()
,尤其是在history模式下。