1.SPA单页面
spa单页面应用程序只有一个index.html,用户输入url的时候会从服务器请求这个文件,之后用户的操作不会利用这个url进行页面加载和或跳转,而是通过前端路由实现html内容在一个页面变换。 优点:
- 不在利用浏览器地址栏发送服务器请求进行页面切换,减少服务器的压力,同时避免了不必要的页面加载和渲染。
- 前后端分离,前端处理用户交互,后端处理数据逻辑。 缺点:
- 初次加载耗时,所有文件都放在一个页面中。所以不要时使用按需加载。
- 不能使用浏览器的前进后退功能,页面切换时需要自己建立堆栈管理。
- 不利于百度、谷歌的爬虫技术对搜索引擎的优化。nuxt.js
2.vue的优势(组件化开发、响应式双向数据绑定)
Vue是一款轻量级渐进式框架,简单易学、双向数据绑定、组件化开发、数据与结构分离、虚拟DOM、运行速度快。 优势:
-
1.
组件化开发
,可以独立开发功能模块
和开发公用模块
提高复用性和工作效率。 -
2.
响应式双向数据绑定
:vue.js会自动对页面中某些数据的变化做出同步的响应
。就是数据驱动页面,页面也可以驱动数据,常用于表单中,用户输入我们就可以在数据模型中拿到数据。响应式系统是我们在开发过程中把关注点更多的放在业务逻辑上面而不是操作dom
。 -
3.单页面应用程序,使页面
局部刷新
,不用每次跳转都请求数据和dom. -
4.生态好,开发工具丰富,大量提高效率.
3.MVVM(Model–View–ViewMode)
- M:Model数据层,后端数据。
- V:View视图层,用户界面。
- VM:ViewMode可以看成是一个响应式系统,通过数据绑定
M——>V
,通过dom事件监听将V——》M
,比如vue实例就是一个响应式系统。 - 描述:用户操作页面视图(dom元素),通过监听dom事件把操作数据传给响应式系统,响应式系统根据这些数据发送ajax请求获取相关数据保存到响应式系统,这些数据更新之后又会通过数据绑定的方式触发视图更新。
4.生命周期
指从组件的创建到销毁的全过程,主要分为三个阶段:挂载阶段——>更新阶段——>销毁阶段。而每个生命周期阶段都有生命周期钩子函数,到达当前阶段就会自动执行钩子函数。 挂载阶段
beforecreate
执行时仅表示vue实例刚好开始创建
,实例中的数据和方法此时还不能调用。
created
钩子函数执行表示实例初始化完成了,可以获得data数据,但Vue 实例使用的根 DOM 元素el还未初始化,一般在这里发送ajax请求
beforeMount
执行时:表示Vue已经编译好了最终模板
, 但是还没有将最终的模板渲染到界面
上,el均已经初始化,但此时el并没有渲染进数据,el的值为“虚拟”的元素节点.
mounted
钩子函数执行表示界面已经渲染完毕上,可以操作dom了.更新阶段
只有数据变化才会调用beforeUpdata
和upDated
,beforeUpdate执行表示el中的数据已经跟新完了,而updated触发时,表示el中的数据已经渲染完成,组件dom被更新。不能在update中修改响应数据`,要么就会形成死循环/销毁阶段
beforeDestroy
钩子函数表示销毁前准备,最后能够访问到组件数据和方法,我们在在这里面可以,解除自定义事件绑定、销毁子组件、事件监听器、定时器等所有的生命周期钩子自动绑定 this 上下文到实例中,所以不能使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos()),会导致this指向父级。
5. 父子组件中的生命周期:
挂载阶段(实例初始化阶段),是先创建父组件,再创建子组件。在父组件编译好了虚拟dom节点(beforeMounted后),就开始进入子组件的挂载阶段,搞完之后就执行父组件的mounted渲染完成
mounted渲染阶段是先保证子组件渲染完,再来渲染父组件父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
更新阶段是:父组件先数据先被被修改(先触发更新),子组件再被修改(再触发更新),子组件更新完了(渲染),父组件才说自己更新完了(渲染)
父beforeUpdate->子beforeUpdate->子updated->父updated 销毁阶段是: 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
6.v-if和v-show
- 概念:两个指令控制元素的显示与隐藏,都是条件渲染指令,当里面的条件为真时,就显示元素,否则不显示。
- 区别:v-if算是真正的条件渲染,它是根据条件销毁和创建元素来实现效果。而v-show元素是一直存在dom结构中的,通过css的display属性控制显示与否。
- v-if适用于很少的切换,v-show适用于频繁切换
7.v-bind
- v-bind专门用于
给元素的属性动态的绑定数据的
, - class给元素绑定动态类名,动态类名不用引号,可以放到数组中、对象中,
- style给元素绑定动态样式
- 还可以动态绑定src、父组件传给子组件的数据等
8.v-on
- v-on指令专门用于给元素绑定监听事件; v-on绑定的事件被触发之后, 会去Vue实例对象的methods中查找对应的回调函数。
- 修饰符-
-
.prevent
阻止元素的默认行为
-
.stop
阻止冒泡,写在谁身上,就不会再往他的上级元素上传递。
-
.self
触发自己才执行, 如果不是触发自己,自己退出捕获冒泡阶段。
-
capture
当前元素的模式是捕获,触发子元素时当前元素最先触发,然后是子元素,中间的从外到里。
-
.native
给组件标签绑定事件之后,不用这个不会绑定成功,相当于把组件变成正常的html标签。
9.v-for
v-for专门用于列表渲染,用item in items
,items是数据源,可为数组或对象。
可以用带有 v-for 的<template>
标签来渲染多个元素块,循环块区域,不但能省去很dom节点,而且也能解决欠套不合理的情况.
在使用v-for指令渲染列表,使用key属性为每个遍历的元素创建唯一标识符
,防止就地复用
(即如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素)。
解释一下。如果我们的列表中有任何动态的UI更改(比如列表项的顺序被打乱),Vue将选择在每个元素中更改数据,而不是相应的移动DOM元素。这在大多数情况下都不是问题。然而,在某种情况下,v-for渲染出来的列表项依赖于DOM状态和(或)子组件状态,这可能会导致一些非预期的渲染效果。
比如:如果渲染模板中包含了一个允许用户直接输入消息的字段,若果打乱顺序,文本会出现意外的匹配。
10. v-model
-
1.v-model指令用于
表单控件元素上创建双向数据绑定
。它会根据控制类型自动选取正确的方法来更新元素。 -
2.v-model本质上是input事件和v-bind的语法糖。输入框绑定一个input,这个事件用于获取用户输入信息
event.target.value
,给输入框同时绑定一个动态属性:value='username'
,然后再绑定函数里面把用户输入的值赋值给value值。 -
3.表单控件-v-model
-
- 单行文本框input和多行文本框textarea双向绑定的是用户输入值。
-
- 单个复选框input>type="checkbox",绑定布尔值;多个复选框v-model绑定同一个属性,属性值为一个数组,数组里面存的是选中状态多选框中的value属性值。
-
- 单选按钮input type="radio",给多个选项的双向数据绑定一个属性,属性值为选中状态单选按钮的value值,是个字符串,
-
- 选择框也分单双section里面双向数据绑定一个属性变量,option 选项里面的value作为保存值。
-
4.一些修饰符
-
v-model.lazy
会在光标离开input框才会更新数据.
-
v-model.trim
输入框过滤首尾的空格
-
v-model.number
输入数字 就会转成是数字
11.数组检测
*1. Vue重写了很多数组原型方法,利用这些方法操作数组数据可以触发视图变化:这些方法,操作data中的原数据,然后同步更新视图。 1.push() 2.pop() 3.shift() 4.unshift() 5.splice() 6.sort() 7.reverse()
- 2.还有一些方法是操作原数组直接返回新数组,不会触发视图更新。可以把新值赋值给原数组就可以触发试图更新。主要有:1.filter(), 2.concat(), 3.slice(), 4.map()。
- 3.显示过滤/排序后的结果,一半不要求改变原数组,可以利用计算属性返回过滤或排序后的数据。
12.计算属性和侦听器
computed计算属性
:
(1)、若果模板中的逻辑太复杂,可以利用计算属性返回一个值,这个值就是计算后属性,不能在data中声明过。
(2)、具体用法:在computed计算属性对象中声明函数,函数名就是计算属性的名字,返回值就是依赖属性通过计算后得到的值。 (3)、若果依赖属性发生变化就重新计算,如果不发生便会,就直接从缓存中获取。
侦听器watch
专门用于监听数据变化的,数据变化了,就会自动调用watch里面的回调方法,监听的属性必须在data中声明过。 监听的属性名作为函数名,函数中接受新值和就值,可以就新值和就值做一些逻辑操作,有immediate: true,立即执行,deep: true, //深度监听。
13.组件注册
- 全局:创建组件构造器Vue.extend({})对象,传入一个配置对象,然后通过 Vue.component("myComponentOne", cnpc)注册,传入自定义组件名和组件对象。然后就可以使用了。
- 局部:就在组件实例的components里面创建并注册,组件名作为键,组件对象作为值。
- 组件内容抽离出来放到script标签里面,模板内容可以抽离出来放到script标签外。
14. 组件的data为啥是一个函数?
- 组件是
可复用的
vue实例,一个组件被创建好之后,就可能被用在各个地方,而组件不管被复用了
多少次,组件中的data数据都应该是相互隔离,互不影响的
。 - 如果组件的data是引用类型的对象,当复用组件时,都指向同一个堆内存,所有的
复用组件操作同一个数据
。 - 而组件的data如果是一个函数,且返回值是一个对象,每次复用组件都会调用data函数,形成独立的数据存储空间。函数执行形成私有栈内存。
15.组件通信(父传子、子传父、兄弟)
1.父传子props
- 父组件使用子组件的时候在子组件的标签中通过v-bind绑定父组件的数据源,在子组件的实例选项中通过props接受。
- 单向传递:Vue 使用单向数据流,就是
父组件的数据变化时会传递给子组件
,反过来则不行。这是为了不让子组件无意修改父组件的状态。- 若果确实想要修改可以把props里面的属性,保存在自己的data里面。
- props的值有两种,一种是数组中存字符串,一种是对象存对象,可以定义传入类型和默认值。 模板中不能使用驼峰
2.子组件通过$emit()
向父组件触发事件(并携带数据)
当子组件的事件触发时,可以在事件里面通过$emit('自定义事件名',子组件数据),然后父组件使用子组件的时候在子组件的标签里面通过v-on接受并绑定自定义事件。
3.兄弟组件-事件总线(EventBus)
- 定义一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件。可以在Vue.prototype.$EventBus = new Vue()
- A组件中通过
Event.$emit(事件名,数据);
传送给事件总线。 - B组件想要接受自定义事件和和数据,用
Event.$on(事件名,data => {});
- 可以通过Event.$off()销毁事件。
此外,还有作用于插槽可以子传父;vuex可以任意通信,
16. slot分发内容
- 父组件想要往子组件的标签中插入一些东西,就会用到slot插槽。
- slot 通俗的理解就是“占坑”,
用插槽在子组件模板中占好了位置
,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中slot位置).
基本使用
第一步:在子组件的模板中写上插槽标签
<slot>默认内容</slot>
; 第二步:父组件使用子组件时,在子组件的标签中写上想插入的内容,如<one>hello,蔡徐坤</one>
插槽标签里面可以添加默认内容,如果父组件不添加插入样式,就会显示默认内容。
需要在子组件分发多个内容,用到具名插槽,添加name属性取个名字,放到相应位置,父组件使用的时候可以在外层元素中用v-slot:插槽名
指定插入的位置。可以简写为#插槽名。
作用域插槽
- 父组件插入子组件插槽的内容板块,作用域是父组件,只能访问父组件的数据和方法,若果想要访问子组件的数据和方法,需要用到作用于插槽。 步骤:
第一步:给子组件的slot标签动态绑定子组件中的要传输数据;v-bind
第二步:父组件中用v-slot为name值设置一个插槽别名:
v-slot:name名="插槽别名"
第三步:通过插槽别名
slotProps
就可以使用数据了
独占默认插槽缩写
当只使用一个默认插槽时,我们想要传子组件的数据,可以把子组件标签作为外层元素使用,写上v-slot='...'
即可
应用:用户列表组件里面会插入一些button作为分发内容,进行增删改查,这些butto需要用子组件的用户ld,通过作用域插槽传过来。
17. $nextTick()和refs
- Vue出于性能的考虑会进行
异步渲染
:就是当data改变之后,dom不会立刻渲染,而是等到数据不再变化的时候 一次性的 将 数据的改变更新到视图中
。- DOM 更新后,会执行
this.$nextTick(()=>{})
的回调函数,在回调函数里面进行一些相关操作,能让我们在操作数据之后,获取最新的dom节点
。- 我们可以结合
$refs来获取dom节点
。先在元素标签里面添加ref字段ref=xxx
,然后通过this.$refs.xxx
获取dom节点,这是个只读属性
。
18. 动态、异步组件
动态组件
- 动态组件是vue原生自带的组件,用来动态显示组件的;
- 它有一个is属性,通过
v-bind:is="动态组件名"
属性,来选择要挂载的组件。 - 动态组件每次切换总会创建和销毁dom,消耗性能。
- 通过
<keep-alive>
标签来告诉 Vue,去缓存已经被渲染过的 component。
异步组件
-
项目中会有很多组件,不是每次都用,在不用的时候,可以让有些组件不被渲染,只有用到它的时候才会被渲染,且会把结果缓存起来共未来使用,我们把这些组件称为异步组件。
-
通过
动态引入
的方法将组建变为异步组件,用一个回调函数,返回值是import()执行结果。传入参数组件的路径。 在webpack中可以这样构建:
// 全局注册
Vue.component(
'async-webpack-example',
// 这个 `import` 函数会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
// 局部注册
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
通过注册路由方法注册异步组件:
// 注册路由的时候会有这么一条语句,实际上是注册异步组件,
// `import` 函数会返回一个 `Promise` 对象
// 将异步组件和 webpack 的 code-splitting 功能一起配合使用
component: () => import(/* webpackChunkName: "index" */ './views/Index.vue')
19. keep-alive缓存组件
1、啥是 keep-alive?
- keep-alive 是 Vue 的内置组件,食用他是为了避免组件反复的创建和销毁,并且保存组件状态。 看看具体的使用场景:
- 第一种比较普遍的场景,当我们从首页–>列表页–>商详页–>再返回,这时候列表页应该是需要keep-alive的。
- 第二种,当我们从首页–>列表页–>商详页–>返回到列表页(需要缓存)–>返回到首页(需要缓存)–>再次进入列表页(不需要缓存),这时候就是按需来控制页面的keep-alive了。
- 两条属性:
include -白名单匹配
, 字符串或正则表达式。只有名称匹配的组件会被缓存。exclude - 黑名单匹配
,字符串或正则表达式。任何名称匹配的组件都不会被缓存。- max - 数字。最多可以缓存多少组件实例。
- 两个生命周期函数
在被keep-alive包裹的
组件
中,会多出两个钩子函数:activated 和 deactivated。当组件在 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。
- 1.activated:在 keep-alive
组件激活
时调用
- 使用 keep-alive 会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,需要在
activated 阶段获取数据
,承担原来 created 钩子函数中获取数据的任务。
- 2.deactivated:在 keep-alive 组件停用时调用
在动态组件中的应用
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
<component :is="currentComponent"></component>
</keep-alive>
- 1.一般情况下,缓存组件包裹路由出口,在路由出口下展示的组件都会被缓存。
- 2.在路由规则下,给路由元信息mata对象添加字段
keepAlive:false
,判断是否需要缓存,这个字段作为被缓存组件包裹的router-view
的条件进行判断是否需要缓存。v-if="$route.meta.keepAlive"
.
现在针对场景按需设置组件缓存
watch: {
$route(to, from) {
// 如果要to(进入)的页面是需要keepAlive缓存的,把name push进include数组中
if (to.meta.keepAlive) {
!this.include.includes(to.name) && this.include.push(to.name);
}
// 如果 要 form(离开) 的页面是 keepAlive缓存的,
// 再根据 deepth 来判断是前进还是后退
// 如果是后退:
if (from.meta.keepAlive && to.meta.deepth < from.meta.deepth) {
const index = this.include.indexOf(from.name);
index !== -1 && this.include.splice(index, 1);
}
}
}
};
20.mixin组件抽离公共逻辑
mixin组件用于抽离组件的公共逻辑部分,可以写在一个js文件里面,在里面写上vue实例的的各个项,data,methods,create,等,,,然后导出这个文件,在其他组件中直接导入引入就会合并vue实例的一些项。
21.自定义指令及过滤器
自定义指令
- 自己可以封装自定义指令对普通 DOM 元素进行底层操作,以达到复用的目的。
- 通过vue.directive()构造器创建,传入一个组件名和配置对象,配置对象的生命周期函数名后面定义一个函数。写入指令的业务逻辑
vue.directive('自定义指令名称', {
生命周期名称: function (el) {
指令业务逻辑代码
}
});
钩子函数:
- bind当把指令绑定到dom元素身上的时候就会执行,在这里可以进行一次性的初始化设置。
- inserted被绑定的元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)
过滤器一般用于格式化插入的文本数据。
22.虚拟dom
-
虚拟DOM对象就是普通的JavaScript对象,访问JavaScript对象要比访问真实的DOM 要快的多,Vue 在更新真实的 DOM 前,会
比较更新前后 虚拟DOM 结构中有差异
的部分,然后采用异步更新队列的方式将差异部分更新到真实的 DOM。 -
Vue提供的
render()函数
是就是让我们使用JavaScript编程的方式生成html模板。而在大多数情况下,我们在 Vue 实例中使用模板template来构建HTML。
23.过渡
- 过渡常用于元素变化的时候出现缓动效果,比如淡入淡出、平滑滚动,减少视觉冲击,提高用户体验。
- 给元素用transition包住,然后利用过度类名在样式表中编辑。
24.Vue-Router路由
- vue-router是vue官方提供的路由管理器,通过改变
路由路径
匹配到相应的路由组件
,然后在路由出口进行展示; - 所以可以先设置好路由映射表,每一个route都应有path和component,当切换path路径时,url就会变化,路由视图取也会根据对应组件进行相应的渲染。
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
安装——》router.js中引入,并使用——》配置路由规则——》实例化路由对象——》导出路由对象——》挂载到根组件中——》设置跳转导航(点击之后url变化)——》
1.路由模式(hash和history)
-
hash:
在浏览器url中,#包括他后面的字符就是hash;可以用window.location.hash读取。hash虽然在url中但不被包括在http请求中;hash值的变化不会导致浏览器重新发送请求,但是会被浏览器记录下来,可以前进后退。改变 hash(浏览器的前进后退),会触发 hashchange 事件。可以获取新旧url。 -
history
: -
HTML5引入了
history.pushState
() 和history.replaceState()
方法,它们分别可以添加
和修改历史记录
条目,这两个方法 可以在改变 url 同时,不会刷新页面,所以在 HTML5 中的 histroy 具备了实现前端路由的能力。 -
需要后台配置支持。如果刷新时,服务器没有响应响应的资源,会刷出404。我们需要在服务器端做处理:如果匹配不到任何静态资源,则应该始终返回同一个 html 页面。
2.动态路由匹配
我们经常需要
把某种模式匹配到的所有路由,全都映射到同个组件
,就会用到动态路由。动态路由是使用动态路径参数来设置的。在定义path路径时,以:
为标识符,设置动态路径参数。在使用的时候(路由跳转),然后就可以通过this.$route.params
获取
//route
{ path: '/user/:id', component: User }
//这样设置后像,路径为`/user/xxx`的都会映射到`/user/`路由
使用动态路由配置之后,所有的路由对应
同一个组件
,此时组件的生命周期钩子不会再被调用
。如果你想路径切换时,进行一些初始化操作时,可以用以下两种解决办法:
* 在组件内watch(监测变化) $route 对象:
```js
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
}
}
或者.
beforeRouteUpdate
导航守卫如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1 -> /users/2),你需要使用 beforeRouteUpdate来响应这个变化 (比如抓取用户信息)。
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
3.路由组件传参(params、query)
- 1.Params传参:路由跳转设置只能使用name,不能使用path,参数不会显示在路径上,浏览器强制刷新参数会被清空。
- 2.Query:不用动态路径参数,参数会显示在路径上,刷新不会被清空可以使用name 或path设置路由路径。
// 传递参数
this.$router.push({
name: Home,
params: {
number: 1 ,
code: '999'
}
})
// 接收参数
const p = this.$route.params
// 传递参数
this.$router.push({
name: Home,
query: {
number: 1 ,
code: '999'
}
})
// 接收参数
const q = this.$route.query
- 3.props传参 设置动态路由之后就可以通过设置 props: true 字段,将此时route.params (即此处的name)将会被设置为组件属性。
//路由配置中
{ path: '/hello/:name', component: Hello, props: true },
//组件中接受
props: ['name'],
4.router,routes,route傻傻分不清?
- 1.router:一般指的就是路由实例.如$router.是路由管理者。
- 2.routes:指路由实例的配置项,是一个数组.用来配置多个route路由对象.
- 3.route:指的就是路由对象.例如;$route指的就是当前路由对象.
- 操作路由跳转的时候,路由器就会去路由集合数组里面查找路径匹配的路由。
router
:路由实例对象对象提供了一些全局可用功能函数,来实现编程式导航。
- $router.push()//会添加记录,可以前进后退
- router.replace//跳转指定路径,会替换当前页面,常见于权限验证,验证后就不让用户回退到登录页重复验证。
- router.go(n)//表示前进或后退多少页
routes
创建vue-router路由实例的配置项。用来配置多个route路由对象
route
获取某个路由信息-包括path\name\meta\params\query\
5. 嵌套路由
一个
<router-view/>
对应展示的就是一个组件,因此实现嵌套路由有两个要点:
- 路由对象中定义子路由(嵌套子路由)
- 组件内
<router-view/>
的使用.
6.命名视图(让路由规则里面的同级普通组件显示)
我们知道点击当前路由路径跳转链接就会显示对应的路由组件,但是如果我们想让让他跳转的同时也把他的兄弟非路由组件显示出来,就需要在同级默认路由出口的地方增加有名字的路由出口,供同级非路由组件显示。看图就明白了。
7.路由重定向和别名
7.1路由重定向
重定向其实就是通过
路由拦截path
,然后替换url跳转到redirect
所指定的路由(默认path,可以设置name)上. 重定向是通过 routes 配置来完成,
7.2 别名
- 设置别名让一个一个路由有两个路径.两个路径都能跳转到该路由.
- /a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。
{ path: '/a', component: A, alias: '/b' }
8. 路由懒加载
因为是单页面应用程序,所有的组件都放在也个页面,会造成打开时加载速度过慢,使用路由懒加载技术可以使组件按需加载,即用到的时候才去加载组件,这样加快了首屏渲染速度。
vueRouter的懒加载主要是靠Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。只需要将组件以promise形式引入即可.
{
path: "/about",
name: "About",
alias: "/a1111",
component: () => import("../views/About.vue"),
},
或者
const Foo = () => import('./Foo.vue')
routes: [
{
path: '/foo',
component: Foo
}
]
//或者把组件按组分块
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
9. 导航守卫(路由钩子)
路由导航守卫,通俗点说就是路由钩子.作用也和生命周期钩子类似,在路由跳转过程进行操作控制. 路由守卫一般用作路由跳转的一些验证,比如登录鉴权(没有登录不能进入个人中心页)等等等
10.1 导航守卫分类
全局守卫有三种:
- 1.router.beforeEach(全局前置守卫)-判断是否登录、开启进度条等 转到点击的页面。
- 2.router.beforeResolve(全局解析守卫)
- 3.router.afterEach(全局后置守卫)-关闭进度条
1.全局前置守卫
:每次路由跳转,router.beforeEach都会执行,并且接受三个参数,to记录着将要进入的目标路由对象的详细,from记录着即将离开的目标路由对象的信息,next()表示执行下一步,即进入下一个钩子,里面可以传false中断导航,或者路由路径对象,router.beforeEach就是全局路由跳转时触发执行的函数。
next:Function,必须需要调用的方法,具体的执行效果则依赖next方法调用的参数
next():放行,直接跳转到to
next(false):终断当前的导航。
next('/')||next({path:'/'}):跳转到一个不同的地址。当前导航终端,执行新的导航。
应用场景
:--用户在未登录的时候进入任意页面,我们就让用户跳转到登录页面,在已登录的时候让用户正常跳;我们所需要做的就是只要路由发生改变并且目标不是登陆路由的时候,我们就要去判断这个用户有没有登陆过,如果没有就跳转到登陆页让他去登陆,否则说明他登陆过,那么我们就可以让他正常访问;
router.beforeEach((to, from, next) => {
const tokensign = window.sessionStorage.getItm('token')
//to.path 目标路由
//next 必须有否则程序不会继续执行
if(to.path !== '/login' && !tokensign) { return next('/login') }
else next()
})
- 2.
全局解析守卫beforeResolve
在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后
,解析守卫就被调用。3.全局后置守卫afterEach
:没有 next 函数也不会改变导航本身,
4.路由独享的守卫
:可以在路由配置的route对象,直接定义 beforeEnter 守卫:用法和全局前置守卫一样,比如进入这个页面之前必须要登陆。
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
// 使用方法和上面的beforeEach一毛一样
}
}
]
})
- 5.
组件内的守卫
:beforeRouteEnter
、beforeRouteUpdate (2.2 新增)
、beforeRouteLeave
你可以在路由组件内直接定义的路由导航守卫: 进入该路由时执行,该路由中参数改变时执行,离开该路由时执行。
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
//不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
next(vm => {
// 通过 `vm` 访问组件实例
})
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
this.name = to.params.name
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 离开当前路由,此时可以用来保存数据,或数据初始化,或关闭定时器等等
// 可以访问组件实例 `this`
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
}
10.2 完整的导航解析流程
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
10.3导航守卫参数
每个守卫方法接收三个参数:
- 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() 注册过的回调。
11. 路由元信息
路由配置的时候,给每个路由对象添加一个自定义的meta对象,在meta对象中可以设置一些状态,来进行一些操作。用它来做登录校验再合适不过了,要优雅要隐性地传递信息,就使用meta对象吧!
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
})
/*
$route.matched: 一个数组,共含当前路由的所有嵌套路径片段的路由记录
*/
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
//数组some方法,如果meta.requiresAuth为ture,则返回true.此时,说明进入该路由前需要判断用户是否已经登录
if (!auth.loggedIn()) { //如果没登录,则跳转到登录页
next({
path: '/login',
query: { redirect: to.fullPath } //官方例子的这个小细节很好,通过query将要跳转的路由路径保存下来,待完成登录后,就可以直接获取该路径,直接跳转到登录前要去的路由
})
} else {
next()
}
} else {
next() // 确保一定要调用 next()
}
})
- 我们可以通过在meta里设置的状态,来判断是否需要进行登录验证.如果meta里的requiresAuth为true,则需要判断是否已经登录,没登录就跳转到登录页.如果已登录则继续跳转.
- 此时,可能会有同学说,前面说的path,params,query都可以存储信息,作为登录验证的状态标记.的确,它们也可以达到同样的效果.如果是少量单个的验证,使用它们问题不大.
- 但如果是多个路由都需要进行登录验证呢?path,params,query是把信息显性地存储在url上的.并且多个路径都把一个相同的状态信息加在url上.这样就使url不再单纯,并且也很不优雅美观.所以要优雅要隐性地传递信息,就使用meta对象吧!
25.vuex全局数据管理
- VueX是实现组件
全局状态数据管理
的一种机制,可以方便的实现组件之间数据的共享
,使得每个组件都可以去vuex中拿数据。它相当于一个公共仓库
,保存着所有组件都能共用的数据
。并且这些数据都是响应式的
. 使用步骤:安装——》导入并使用——》创建store实例对象并导出export default new Vuex.Store({。。。})
——》挂载到全局vue实例对象
vue实例对象中有几个配置项:
1.state
- state提供唯
一公共数据源
,所有共享的数据都要统一放到Store的state属性中进行存储- 组件获取state共享数据this.$store.state.xxx
- 或者组件中导入mapState方法,然后在组件的计算属性里面调用方法,可以把state中的数据映射为自己的数据使用
...mapState(["count"])
2 mutation
- mutation是用于
变更state中的数据
的唯一方法,使用这种方法可以集中监控所有数据的变化. 组件想要改变state中的数据,必须在mutations中定义方法,在组件中通过this.$store.commit('方法名')
提交过去。当然也可以直接在组件中引入vuex中的mapMutations
,使用(...mapMutations(['方法名'])
)可以直接将mutation中的方法映射成成自己的methods方法。 actions- 若果需要异步操作state数据,必须通过action执行异步操作,然后再触发mutation函数间接变更数据。
- 第一步:actions中定义异步函数,在异步函数中,通过(
context.commit("add",step)
)提交给mutations中的函数(后面的参数可选),
- 第二步:组件中的方法里面用dispatch分发actions里面的异步函数
this.$store.dispatch("addAsync",34);
另外一种方法:引入mapActions函数,使用这个函数把,actions里面的异步函数映射成自己的,就可以直接使用...mapActions(["subAsync"])
4 getter
getter用于对store中的数据进行加工处理形成新的数据, 类似vue中的计算属性.- store中的数据变化,getter中的数据也会变化,getter依赖于state中的数据。
- 获取getter中的数据,可以使用
$store.getters.showNum
或者用mapGetters函数映射为组件的计算属性。
computed:{
...mapGetters(['showNum'])
}
vuex实现购物车
- 在商品页面点击添加购物车,发送axios请求(传入参数:商品id和商品是否选中状态selected),成功之后后同步到vuex中去,共享商品数量,然后还需要跳转到购物车页面
this.$store.dispatch('savaCartCount',res.cartProductVolist)
- 购物车页面的created钩子函数里面请求购物车商品列表,并保存下来
data{
list:[],
allChecked:false,//是否全选
totalPrice:0//总价格
checkedNum:0//选中商品数量
}
//进入购物车初始化数据
getCartList(){
this.axios.get('/cart').then(res=>{
//若果购物车为空就返回空数组
this.list=res.cartProductVolist||[];
this.allChecked=res.selectAll;
this.totalPrice=res.CartTotalPrice;
//过滤商品数量,选中的数量才加进去
this.checkedNum=this.list.filter(item=>item.ProductSelected)
})
}
//全选与不全选,给全选按钮榜一个事件
toogle(){
let url=this.allChecked?'/cart/unSelectAll':'cart/selectAll';
this.axios.put(url).then(
res=>{
//重新初始化
this.list=res.cartProductVolist||[];
this.allChecked=res.selectAll;
this.totalPrice=res.CartTotalPrice;
//过滤商品数量,选中的数量才加进去
this.checkedNum=this.list.filter(item=>item.ProductSelected)
})
}
//跟新购物车:+、-、当前商品是否选中
upData(item,type){
let quantity=item.quantity;
let selected=item.productSelected
if(type=='-'){
if(quantity==1){
return
}
--quantity
}}else if(type=='+'){
if(quantity>item.productStock){return}
++quantity
}else{
selected=!item.productSelected
}
this.axios.put(`cart/${item.productId}`).then(
//更新list和全选,总价,总数
)
}
//删除购物车商品
delProduct(){
//调接口传入商品id,在重新获取list和全选、总价、总数
}
//点击去结算
order(){
let isCheck=this.list.every(item=>!item.productedSelected)
if(isCheck){
//没有选择商品直接返回
return
}else{
//选中了商品跳转到下单页面
}
}